diff --git a/src/editor/lsp/actions.ts b/src/editor/lsp/actions.ts index 4838348b0..696e50935 100644 --- a/src/editor/lsp/actions.ts +++ b/src/editor/lsp/actions.ts @@ -14,7 +14,8 @@ import { expressionTypeInfo } from "./typeInfo"; import { gotoHighlightedUsage, gotoTargets, highlightUsages } from "./navigation"; import { gotoDiagnostic, showErrorDescription } from "./diagnostics"; import { applyKindSilently, applyQuickFix, runKindAction, showIntentions } from "./intentions"; -import { implementPythonAbstractMethods } from "./pythonMembers"; +import { docOf } from "./lsp"; +import { languageSemantics } from "./semantic"; import { workflowSettings, type ActionsOnSaveValid } from "@/platform/workflowSettings"; import { startRename } from "./rename"; import { formatDocument, reformatDialog } from "./format"; @@ -340,7 +341,7 @@ export function registerLspActions(): () => void { title: IMPLEMENT_TITLE, none: "Nothing to implement", label: "Implement Members", - fallback: () => implementPythonAbstractMethods(v), + fallback: () => languageSemantics(docOf(v)?.language)?.implementMethods?.(v) ?? Promise.resolve(false), }), ), editorAction("code.overrideMethods", "Override Methods…", "Code", (v) => diff --git a/src/editor/lsp/completion.ts b/src/editor/lsp/completion.ts index f1e4cd8aa..ce24ff4a9 100644 --- a/src/editor/lsp/completion.ts +++ b/src/editor/lsp/completion.ts @@ -56,7 +56,7 @@ import { editorPrefs } from "@/platform/editorSettings"; import { tx } from "@/platform/i18n"; import { showHint } from "./hint"; import { inplaceRenameActive } from "./inplaceRename"; -import { phpConstructDocumentation } from "./phpConstructs"; +import { languageSemantics } from "./semantic"; interface RequestContext { doc: number; @@ -369,7 +369,7 @@ async function fallback(ctx: CompletionContext): Promise { const own = contributedAt(view.state, CONTRIBUTED_HOVER, pos); if (own) { @@ -49,7 +54,7 @@ export async function requestHover(view: EditorView, pos: number): Promise languageSemantics(docOf(view)?.language)?.intentions?.(view) ?? []), + ]; } diff --git a/src/editor/lsp/semantic.test.ts b/src/editor/lsp/semantic/index.test.ts similarity index 95% rename from src/editor/lsp/semantic.test.ts rename to src/editor/lsp/semantic/index.test.ts index 8f2f89c6a..a84769000 100644 --- a/src/editor/lsp/semantic.test.ts +++ b/src/editor/lsp/semantic/index.test.ts @@ -13,8 +13,8 @@ vi.mock("@/platform/ipc", async (orig) => { return { ...actual, isTauri: () => true, commands: { ...actual.commands, lspSemanticTokens } }; }); -const { semanticExtension } = await import("./semantic"); -const { lspDoc } = await import("./lsp"); +const { semanticExtension } = await import("./index"); +const { lspDoc } = await import("../lsp"); const views: EditorView[] = []; afterEach(() => { diff --git a/src/editor/lsp/semantic.ts b/src/editor/lsp/semantic/index.ts similarity index 81% rename from src/editor/lsp/semantic.ts rename to src/editor/lsp/semantic/index.ts index e19050eed..c9ed6ce4d 100644 --- a/src/editor/lsp/semantic.ts +++ b/src/editor/lsp/semantic/index.ts @@ -8,22 +8,26 @@ * identifiers): local variables and parameters additionally get * `cm-sem-rainbow-`, `n` derived from the name, so one name keeps one * colour (`--syn-rainbow-`) throughout the file. + * + * Language-specific behaviour lives in the `python/` and `php/` folders, + * registered into `languages.ts`; everything here is language-agnostic. */ import { syntaxTree } from "@codemirror/language"; import type { Tree } from "@lezer/common"; -import { - pythonSyntaxRanges, - pythonFallbackRanges, - pythonSymbolClasses, - pythonSymbolKinds, - type PythonSymbolKind, -} from "./pythonHighlight"; import { Prec, StateEffect, StateField, type Extension, type Text } from "@codemirror/state"; import { Decoration, EditorView, ViewPlugin, type DecorationSet, type ViewUpdate } from "@codemirror/view"; import { commands, type SemanticTokens } from "@/platform/ipc"; import { editorPrefs } from "@/platform/editorSettings"; -import { debounce, docOf, isUnavailable, lspRequest } from "./lsp"; -import { featureBackoff, onLspRefresh } from "./availability"; +import { debounce, docOf, isUnavailable, lspRequest } from "../lsp"; +import { featureBackoff, onLspRefresh } from "../availability"; +import { languageSemantics, type SemanticRange } from "./languages"; + +// Language folders register their semantics by importing this module; lookups +// elsewhere go through the re-exported `languageSemantics`. +export { languageSemantics } from "./languages"; +// Registers each language's semantics (highlighting hooks, construct docs). +import "./python"; +import "./php"; const semanticBackoff = featureBackoff(); @@ -89,33 +93,6 @@ export function rainbowIndex(name: string): number { /** Locals and parameters only: no fields, statics, constants, library or `self` names. */ const RAINBOW_TYPES = new Set(["variable", "parameter"]); const NOT_LOCAL = new Set(["static", "constant", "defaultLibrary", "global", "classScope", "fileScope"]); -const PYTHON_VARIABLE_KINDS = new Set([ - "localVariable", - "globalVariable", - "instanceAttribute", - "classAttribute", - "constant", - "property", - "typeParameter", - "unusedParameter", -]); -const PYTHON_SYMBOL_BASES = new Set([ - "cm-sem-variable", - "cm-sem-global", - "cm-sem-property", - "cm-sem-constant", - "cm-sem-type", - "cm-sem-typeParameter", - "cm-sem-parameter", - "cm-sem-builtin", -]); - -export interface SemanticRange { - from: number; - to: number; - className: string; - source?: "pythonSyntax" | "pythonFallback"; -} function rangeCoverage(ranges: Array<{ from: number; to: number }>) { ranges.sort((a, b) => a.from - b.from || a.to - b.to); @@ -141,8 +118,9 @@ export function semanticRanges( language?: string, tree?: Tree, ): SemanticRange[] { - const symbols = language === "python" && tree ? pythonSymbolKinds(tree, doc) : undefined; - const syntax = language === "python" && tree ? pythonSyntaxRanges(tree, doc, symbols) : []; + const semantics = languageSemantics(language); + const symbols = semantics?.symbolKinds && tree ? semantics.symbolKinds(tree, doc) : undefined; + const syntax = semantics?.syntaxRanges && tree ? semantics.syntaxRanges(tree, doc, symbols ?? new Map()) : []; const covered = rangeCoverage(syntax); const serverRanges: Array<{ from: number; to: number }> = []; const out: SemanticRange[] = []; @@ -161,7 +139,7 @@ export function semanticRanges( const to = Math.min(from + len, l.to); if (from >= to) continue; const type = tokenTypes[typeIdx] ?? ""; - const pythonKind = symbols?.get(`${from}:${to}`); + const kind = symbols?.get(`${from}:${to}`); const names: string[] = []; for (let b = 0; b < tokenModifiers.length; b++) if (mods & (1 << b)) names.push(tokenModifiers[b]!); const classes: string[] = []; @@ -174,22 +152,14 @@ export function semanticRanges( if (base && base !== "variable") classes.push(`cm-sem-${base}`); // `hasOwn`: servers send names like `constructor` (jdtls) that `Object.prototype` also has. for (const n of names) if (Object.hasOwn(MODIFIER_CLASS, n)) classes.push(`cm-sem-${MODIFIER_CLASS[n]}`); - if (language === "python" && names.includes("static")) classes.push("cm-sem-python-static"); - if (language === "python" && type === "method") classes.push("cm-sem-python-method-call"); - if (language === "python" && names.includes("defaultLibrary")) classes.push("cm-sem-python-builtin"); - if (pythonKind && PYTHON_VARIABLE_KINDS.has(pythonKind)) { - for (let j = classes.length - 1; j >= 0; j--) if (PYTHON_SYMBOL_BASES.has(classes[j]!)) classes.splice(j, 1); - classes.push(...pythonSymbolClasses(pythonKind).split(" ")); - } + semantics?.tokenClasses?.({ type, modifiers: names, kind, classes }); const local = rainbow && !constant && !names.includes("selfParameter") && !names.some((n) => NOT_LOCAL.has(n)) && - (language === "python" - ? pythonKind - ? pythonKind === "localVariable" || pythonKind === "parameter" - : type === "parameter" + (semantics?.isLocal + ? semantics.isLocal({ type, modifiers: names, kind }) : RAINBOW_TYPES.has(type) && (type === "parameter" || !marksLocals || names.includes("local"))); serverRanges.push({ from, to }); if (!classes.length && !local) continue; @@ -197,15 +167,17 @@ export function semanticRanges( if (local) classes.push(`cm-sem-rainbow-${rainbowIndex(doc.sliceString(from, to))}`); out.push({ from, to, className: classes.join(" ") }); } - if (language === "python" && tree) { + if (semantics?.fallbackRanges && tree) { const serverCovered = rangeCoverage(serverRanges); out.push( - ...pythonFallbackRanges( - tree, - doc, - rainbow ? (name) => `cm-sem-rainbow-${rainbowIndex(name)}` : undefined, - symbols, - ).filter((r) => !covered(r.from, r.to) && !serverCovered(r.from, r.to)), + ...semantics + .fallbackRanges( + tree, + doc, + rainbow ? (name) => `cm-sem-rainbow-${rainbowIndex(name)}` : undefined, + symbols ?? new Map(), + ) + .filter((r) => !covered(r.from, r.to) && !serverCovered(r.from, r.to)), ); } out.push(...syntax); @@ -213,11 +185,11 @@ export function semanticRanges( } const markCache = new Map(); -const markFor = (cls: string, source?: "pythonSyntax" | "pythonFallback", symbol?: string) => { +const markFor = (cls: string, source?: SemanticRange["source"], symbol?: string) => { if (symbol !== undefined) return Decoration.mark({ class: cls, symbol }); const key = `${source ?? "semantic"}:${cls}`; let m = markCache.get(key); - if (!m) markCache.set(key, (m = Decoration.mark({ class: cls, pythonSyntax: source !== undefined }))); + if (!m) markCache.set(key, (m = Decoration.mark({ class: cls, languageSyntax: source !== undefined }))); return m; }; @@ -270,15 +242,15 @@ const semanticPlugin = ViewPlugin.fromClass( } refreshSyntax(doc: Text) { const tree = syntaxTree(this.view.state); - const python = docOf(this.view)?.language === "python"; - const symbols = python ? pythonSymbolKinds(tree, doc) : undefined; - const syntax = python ? pythonSyntaxRanges(tree, doc, symbols) : []; + const semantics = languageSemantics(docOf(this.view)?.language); + const symbols = semantics?.symbolKinds ? semantics.symbolKinds(tree, doc) : undefined; + const syntax = semantics?.syntaxRanges ? semantics.syntaxRanges(tree, doc, symbols ?? new Map()) : []; const covered = rangeCoverage(syntax); const retained: Array<{ from: number; to: number }> = []; const marks = syntax.map((r) => markFor(r.className, r.source).range(r.from, r.to)); this.view.state.field(semanticField).between(0, doc.length, (from, to, decoration) => { if ( - !decoration.spec.pythonSyntax && + !decoration.spec.languageSyntax && !covered(from, to) && decoration.spec.symbol === doc.sliceString(from, to) ) { @@ -286,13 +258,13 @@ const semanticPlugin = ViewPlugin.fromClass( retained.push({ from, to }); } }); - if (docOf(this.view)?.language === "python") { + if (semantics?.fallbackRanges) { const retainedCovered = rangeCoverage(retained); - for (const r of pythonFallbackRanges( + for (const r of semantics.fallbackRanges( tree, doc, this.rainbow ? (name) => `cm-sem-rainbow-${rainbowIndex(name)}` : undefined, - symbols, + symbols ?? new Map(), )) if (!covered(r.from, r.to) && !retainedCovered(r.from, r.to)) marks.push(markFor(r.className, r.source).range(r.from, r.to)); diff --git a/src/editor/lsp/semantic/languages.ts b/src/editor/lsp/semantic/languages.ts new file mode 100644 index 000000000..1808442ca --- /dev/null +++ b/src/editor/lsp/semantic/languages.ts @@ -0,0 +1,73 @@ +/** + * Per-language semantics behind `lsp/semantic`: each language folder + * (`python/`, `php/`) registers a provider here, and feature code looks it up + * by the document's language instead of branching on language names. + */ +import type { EditorState, Text } from "@codemirror/state"; +import type { EditorView } from "@codemirror/view"; +import type { Tree } from "@lezer/common"; +import type { ContributedIntention } from "@/editor/api"; + +/** One decorated range; `source` says which parse-tree pass produced it. */ +export interface SemanticRange { + from: number; + to: number; + className: string; + source?: "syntax" | "fallback"; +} + +/** A server token, with the classes the generic mapping already produced. */ +export interface SemanticToken { + type: string; + modifiers: readonly string[]; + /** Kind from the language's own `symbolKinds` classification, when present. */ + kind: string | undefined; + /** Classes accumulated so far; the hook appends or removes. */ + classes: string[]; +} + +/** Documentation of a language construct that has no source declaration. */ +export interface ConstructDoc { + from: number; + to: number; + markdown: string; +} + +export interface LanguageSemantics { + /** Classifies identifiers from the parse tree (`"from:to"` → kind). */ + symbolKinds?(tree: Tree, doc: Text): Map; + /** Parse-tree ranges styling what the LSP tokens do not cover. */ + syntaxRanges?(tree: Tree, doc: Text, symbols: Map): SemanticRange[]; + /** Fast local guesses shown while the server is pending. */ + fallbackRanges?( + tree: Tree, + doc: Text, + rainbow: ((name: string) => string) | undefined, + symbols: Map, + ): SemanticRange[]; + /** Extra classes for a server token, on top of the generic ones. */ + tokenClasses?(token: SemanticToken): void; + /** Whether a server token counts as a local for rainbow colouring; default: the generic rule. */ + isLocal?(token: Pick): boolean; + /** Documentation of the language construct called at `pos`, if there is one. */ + constructAt?(state: EditorState, pos: number): ConstructDoc | null; + /** Construct documentation by name (completion item detail). */ + constructDocumentation?(name: string): string | null; + /** Bulb intentions contributed on this language's documents. */ + intentions?(view: EditorView): ContributedIntention[]; + /** Fallback for the Implement Methods action when the server offers no code action. */ + implementMethods?(view: EditorView): Promise; +} + +const registry: Record = {}; + +export function registerLanguageSemantics(languages: string | readonly string[], semantics: LanguageSemantics): void { + for (const language of typeof languages === "string" ? [languages] : languages) { + const id = language.toLowerCase(); + registry[id] = { ...registry[id], ...semantics }; + } +} + +export function languageSemantics(language: string | null | undefined): LanguageSemantics | undefined { + return language ? registry[language.toLowerCase()] : undefined; +} diff --git a/src/editor/lsp/phpConstructs.test.ts b/src/editor/lsp/semantic/php/constructs.test.ts similarity index 90% rename from src/editor/lsp/phpConstructs.test.ts rename to src/editor/lsp/semantic/php/constructs.test.ts index a51aa6b0b..59675e5d2 100644 --- a/src/editor/lsp/phpConstructs.test.ts +++ b/src/editor/lsp/semantic/php/constructs.test.ts @@ -2,11 +2,11 @@ import { afterEach, describe, expect, it } from "vitest"; import { EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { languages } from "@codemirror/language-data"; -import { phpConstructAt, phpConstructDocumentation } from "./phpConstructs"; -import { lspDoc } from "./lsp"; -import { quickDefinition, requestHover } from "./hover"; -import { gotoTargets } from "./navigation"; -import { closePopup, useLspPopup } from "./popupStore"; +import { phpConstructAt, phpConstructDocumentation } from "./constructs"; +import { lspDoc } from "../../lsp"; +import { quickDefinition, requestHover } from "../../hover"; +import { gotoTargets } from "../../navigation"; +import { closePopup, useLspPopup } from "../../popupStore"; const php = await languages.find((l) => l.name === "PHP")!.load(); const views: EditorView[] = []; diff --git a/src/editor/lsp/phpConstructs.ts b/src/editor/lsp/semantic/php/constructs.ts similarity index 100% rename from src/editor/lsp/phpConstructs.ts rename to src/editor/lsp/semantic/php/constructs.ts diff --git a/src/editor/lsp/semantic/php/index.ts b/src/editor/lsp/semantic/php/index.ts new file mode 100644 index 000000000..d5ce459f0 --- /dev/null +++ b/src/editor/lsp/semantic/php/index.ts @@ -0,0 +1,15 @@ +/** + * PHP: no semantic-token extras, but its language constructs (`empty`, `isset`) + * have no source declaration, so hover and completion get their documentation + * from here. + */ +import type { LanguageSemantics } from "../languages"; +import { registerLanguageSemantics } from "../languages"; +import { phpConstructAt, phpConstructDocumentation } from "./constructs"; + +const phpSemantics: LanguageSemantics = { + constructAt: (state, pos) => phpConstructAt(state, pos, "php"), + constructDocumentation: (name) => phpConstructDocumentation("php", name), +}; + +registerLanguageSemantics("php", phpSemantics); diff --git a/src/editor/lsp/pythonHighlight.test.ts b/src/editor/lsp/semantic/python/highlight.test.ts similarity index 98% rename from src/editor/lsp/pythonHighlight.test.ts rename to src/editor/lsp/semantic/python/highlight.test.ts index a47830b7a..49f9d645a 100644 --- a/src/editor/lsp/pythonHighlight.test.ts +++ b/src/editor/lsp/semantic/python/highlight.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from "vitest"; import { EditorState } from "@codemirror/state"; import { ensureSyntaxTree, syntaxTree } from "@codemirror/language"; -import { findLanguageDescription, loadSupport } from "../core/cm/languages"; -import { pythonSyntaxRanges, pythonFallbackRanges, pythonSymbolKinds } from "./pythonHighlight"; -import { semanticRanges } from "./semantic"; +import { findLanguageDescription, loadSupport } from "../../../core/cm/languages"; +import { pythonSyntaxRanges, pythonFallbackRanges, pythonSymbolKinds } from "./highlight"; +import { semanticRanges } from "../index"; async function parse(source: string) { const support = await loadSupport(findLanguageDescription("Python", "demo.py")!); diff --git a/src/editor/lsp/pythonHighlight.ts b/src/editor/lsp/semantic/python/highlight.ts similarity index 98% rename from src/editor/lsp/pythonHighlight.ts rename to src/editor/lsp/semantic/python/highlight.ts index 1892c10c9..c2269bd35 100644 --- a/src/editor/lsp/pythonHighlight.ts +++ b/src/editor/lsp/semantic/python/highlight.ts @@ -1,12 +1,6 @@ import type { SyntaxNode, Tree } from "@lezer/common"; import type { Text } from "@codemirror/state"; - -interface PythonRange { - from: number; - to: number; - className: string; - source: "pythonSyntax" | "pythonFallback"; -} +import type { SemanticRange } from "../languages"; export type PythonSymbolKind = | "function" @@ -478,8 +472,8 @@ export function pythonSyntaxRanges( tree: Tree, doc: Text, symbols: Map = pythonSymbolKinds(tree, doc), -): PythonRange[] { - const ranges: PythonRange[] = []; +): SemanticRange[] { + const ranges: SemanticRange[] = []; tree.iterate({ enter(cursor) { const node = cursor.node; @@ -505,10 +499,10 @@ export function pythonSyntaxRanges( let from = node.from; for (let child = node.firstChild; child; child = child.nextSibling) { if (child.name !== "FormatReplacement") continue; - if (child.from > from) ranges.push({ from, to: child.from, className, source: "pythonSyntax" }); + if (child.from > from) ranges.push({ from, to: child.from, className, source: "syntax" }); from = child.to; } - if (from < node.to) ranges.push({ from, to: node.to, className, source: "pythonSyntax" }); + if (from < node.to) ranges.push({ from, to: node.to, className, source: "syntax" }); return; } const text = doc.sliceString(node.from, node.to); @@ -555,7 +549,7 @@ export function pythonSyntaxRanges( } else if (node.name === "Escape") style = "python-escape"; else if (node.name === "type" && node.parent?.name === "TypeDefinition") style = "keyword"; if (style) className = `cm-sem-${style}`; - if (className) ranges.push({ from: node.from, to: node.to, className, source: "pythonSyntax" }); + if (className) ranges.push({ from: node.from, to: node.to, className, source: "syntax" }); if (style === "python-docstring") return false; }, }); @@ -574,8 +568,8 @@ export function pythonFallbackRanges( doc: Text, rainbow?: (name: string) => string, symbols: Map = pythonSymbolKinds(tree, doc), -): PythonRange[] { - const ranges: PythonRange[] = []; +): SemanticRange[] { + const ranges: SemanticRange[] = []; tree.iterate({ enter(cursor) { const node = cursor.node; @@ -592,7 +586,7 @@ export function pythonFallbackRanges( className: pythonSymbolClasses(kind) + (local && rainbow && name !== "self" && name !== "cls" ? ` ${rainbow(name)}` : ""), - source: "pythonFallback", + source: "fallback", }); }, }); diff --git a/src/editor/lsp/semantic/python/index.ts b/src/editor/lsp/semantic/python/index.ts new file mode 100644 index 000000000..f7e01453b --- /dev/null +++ b/src/editor/lsp/semantic/python/index.ts @@ -0,0 +1,58 @@ +/** + * Python's semantic highlighting beyond the LSP tokens (PyCharm's category + * colours): symbol kinds from the parse tree, syntax ranges the server never + * reports, and fallbacks while the server is pending. + */ +import type { LanguageSemantics } from "../languages"; +import { registerLanguageSemantics } from "../languages"; +import { + pythonFallbackRanges, + pythonSymbolClasses, + pythonSymbolKinds, + pythonSyntaxRanges, + type PythonSymbolKind, +} from "./highlight"; +import { implementPythonAbstractMethods, pythonMemberIntentions } from "./members"; + +/** Server tokens these kinds override: the kind's own classes replace the generic ones. */ +const VARIABLE_KINDS = new Set([ + "localVariable", + "globalVariable", + "instanceAttribute", + "classAttribute", + "constant", + "property", + "typeParameter", + "unusedParameter", +]); +const SYMBOL_BASES = new Set([ + "cm-sem-variable", + "cm-sem-global", + "cm-sem-property", + "cm-sem-constant", + "cm-sem-type", + "cm-sem-typeParameter", + "cm-sem-parameter", + "cm-sem-builtin", +]); + +const pythonSemantics: LanguageSemantics = { + symbolKinds: pythonSymbolKinds, + syntaxRanges: (tree, doc, symbols) => pythonSyntaxRanges(tree, doc, symbols as Map), + fallbackRanges: (tree, doc, rainbow, symbols) => + pythonFallbackRanges(tree, doc, rainbow, symbols as Map), + tokenClasses({ type, modifiers, kind, classes }) { + if (modifiers.includes("static")) classes.push("cm-sem-python-static"); + if (type === "method") classes.push("cm-sem-python-method-call"); + if (modifiers.includes("defaultLibrary")) classes.push("cm-sem-python-builtin"); + if (kind && VARIABLE_KINDS.has(kind as PythonSymbolKind)) { + for (let j = classes.length - 1; j >= 0; j--) if (SYMBOL_BASES.has(classes[j]!)) classes.splice(j, 1); + classes.push(...pythonSymbolClasses(kind as PythonSymbolKind).split(" ")); + } + }, + isLocal: ({ type, kind }) => (kind ? kind === "localVariable" || kind === "parameter" : type === "parameter"), + intentions: pythonMemberIntentions, + implementMethods: implementPythonAbstractMethods, +}; + +registerLanguageSemantics("python", pythonSemantics); diff --git a/src/editor/lsp/pythonMembers.test.ts b/src/editor/lsp/semantic/python/members.test.ts similarity index 96% rename from src/editor/lsp/pythonMembers.test.ts rename to src/editor/lsp/semantic/python/members.test.ts index df4a8f11f..7ddf3996e 100644 --- a/src/editor/lsp/pythonMembers.test.ts +++ b/src/editor/lsp/semantic/python/members.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { classMembers, enclosingClass, memberStubs } from "./pythonMembers"; +import { classMembers, enclosingClass, memberStubs } from "./members"; const src = `from abc import ABC, abstractmethod diff --git a/src/editor/lsp/pythonMembers.ts b/src/editor/lsp/semantic/python/members.ts similarity index 97% rename from src/editor/lsp/pythonMembers.ts rename to src/editor/lsp/semantic/python/members.ts index 1402c2d02..5ac0afd33 100644 --- a/src/editor/lsp/pythonMembers.ts +++ b/src/editor/lsp/semantic/python/members.ts @@ -8,10 +8,10 @@ import { indentUnit } from "@codemirror/language"; import type { EditorView } from "@codemirror/view"; import { commands } from "@/platform/ipc"; import type { ContributedIntention } from "@/editor/api"; -import { docOf, lspRequest, reportLspError } from "./lsp"; -import { offsetToPos } from "./positions"; -import { readFileLines } from "./files"; -import { showHint } from "./hint"; +import { docOf, lspRequest, reportLspError } from "../../lsp"; +import { offsetToPos } from "../../positions"; +import { readFileLines } from "../../files"; +import { showHint } from "../../hint"; const CLASS_HEADER = /^(\s*)class\s+\w+\s*\((.*)\)\s*:/; const CLASS = /^\s*class\s/; diff --git a/src/editor/lsp/semanticTyping.test.ts b/src/editor/lsp/semantic/typing.test.ts similarity index 92% rename from src/editor/lsp/semanticTyping.test.ts rename to src/editor/lsp/semantic/typing.test.ts index 9d4fc85db..d026eec7c 100644 --- a/src/editor/lsp/semanticTyping.test.ts +++ b/src/editor/lsp/semantic/typing.test.ts @@ -1,18 +1,18 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; -import { findLanguageDescription, loadSupport } from "../core/cm/languages"; +import { findLanguageDescription, loadSupport } from "../../core/cm/languages"; import type { SemanticTokens } from "@/platform/ipc"; import type { EditorDocContext } from "@/editor/api"; -import { lspDoc } from "./lsp"; -import { semanticExtension } from "./semantic"; +import { lspDoc } from "../lsp"; +import { semanticExtension } from "./index"; const requests = vi.hoisted(() => [] as Array<(tokens: SemanticTokens) => void>); -vi.mock("./lsp", async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock("../lsp", async (importOriginal) => ({ + ...(await importOriginal()), lspRequest: () => new Promise((resolve) => requests.push(resolve)), })); -vi.mock("./availability", () => ({ +vi.mock("../availability", () => ({ featureBackoff: () => ({ active: () => false, mark: () => {} }), onLspRefresh: () => () => {}, }));