diff --git a/gui/src/gui/completion.ts b/gui/src/gui/completion.ts index 9a57c57..fef8f6d 100644 --- a/gui/src/gui/completion.ts +++ b/gui/src/gui/completion.ts @@ -15,7 +15,7 @@ import { TextDocument, Uri, } from "vscode"; -import { defaultElementList, defaultElementProperties, defaultOnFunctionList } from "./constant"; +import { defaultElementList, defaultElementProperties, defaultOnFunctionList, LanguageId } from "./constant"; import { markdownDocumentFilter, pythonDocumentFilter } from "./utils"; const RE_LINE = /<(([\|]{1})([^\|]*)){1,2}/; @@ -23,13 +23,28 @@ const RE_LINE = /<(([\|]{1})([^\|]*)){1,2}/; export class GuiCompletionItemProvider implements CompletionItemProvider { static register(context: ExtensionContext) { context.subscriptions.push( - languages.registerCompletionItemProvider(markdownDocumentFilter, new GuiCompletionItemProvider(), "|") + languages.registerCompletionItemProvider( + markdownDocumentFilter, + new GuiCompletionItemProvider(LanguageId.md), + "|", + "{", + "=" + ) ); context.subscriptions.push( - languages.registerCompletionItemProvider(pythonDocumentFilter, new GuiCompletionItemProvider(), "|", "{", "=") + languages.registerCompletionItemProvider( + pythonDocumentFilter, + new GuiCompletionItemProvider(LanguageId.py), + "|", + "{", + "=" + ) ); } + private constructor(private readonly language: LanguageId) { + } + public async provideCompletionItems( document: TextDocument, position: Position, @@ -37,58 +52,81 @@ export class GuiCompletionItemProvider implements CompletionItemProvider { ): Promise { const line = document.lineAt(position).text; const linePrefix = line.slice(0, position.character); - let completionList: CompletionItem[] = []; - if ((document.fileName.endsWith(".md") || document.languageId === "markdown") && linePrefix.endsWith("{")) { - const potentialPythonFile = path.join(path.dirname(document.uri.fsPath), path.parse(document.fileName).name + ".py"); - if (existsSync(potentialPythonFile)) { - let symbols = (await commands.executeCommand( - "vscode.executeDocumentSymbolProvider", - Uri.file(potentialPythonFile) - )) as SymbolInformation[]; - symbols = symbols.filter((v) => v.kind === SymbolKind.Variable); - return symbols.map((v) => new CompletionItem(v.name, CompletionItemKind.Variable)); + // md completion + if (this.language === LanguageId.md) { + return this.getMarkdownCompletion(document, linePrefix); + } + // python completion + if (this.language === LanguageId.py) { + return this.getPythonCompletion(document, linePrefix); + } + return []; + } + + private async getMarkdownCompletion(document: TextDocument, linePrefix: string): Promise { + const potentialPythonFile = path.join(path.dirname(document.uri.fsPath), path.parse(document.fileName).name + ".py"); + // variable name completion + if (existsSync(potentialPythonFile)) { + if (linePrefix.endsWith("{")) { + return await this.getSymbols(Uri.file(potentialPythonFile), SymbolKind.Variable, CompletionItemKind.Variable); } + // function name for 'on_*' properties + if (linePrefix.endsWith("=") && defaultOnFunctionList.some((v) => linePrefix.endsWith(v + "="))) { + return await this.getSymbols(Uri.file(potentialPythonFile), SymbolKind.Function, CompletionItemKind.Function); + } + } + return this.getCommonCompletion(document, linePrefix); + } + + private async getPythonCompletion(document: TextDocument, linePrefix: string): Promise { + // variable name completion + if (linePrefix.endsWith("{")) { + return await this.getSymbols(document.uri, SymbolKind.Variable, CompletionItemKind.Variable); } - if ((document.fileName.endsWith(".py") || document.languageId === "python") && linePrefix.endsWith("{")) { - let symbols = (await commands.executeCommand( - "vscode.executeDocumentSymbolProvider", - document.uri - )) as SymbolInformation[]; - symbols = symbols.filter((v) => v.kind === SymbolKind.Variable); - return symbols.map((v) => new CompletionItem(v.name, CompletionItemKind.Variable)); + // function name for 'on_*' properties + if (linePrefix.endsWith("=") && defaultOnFunctionList.some((v) => linePrefix.endsWith(v + "="))) { + return await this.getSymbols(document.uri, SymbolKind.Function, CompletionItemKind.Function); } - if ( - (document.fileName.endsWith(".py") || document.languageId === "python") && - linePrefix.endsWith("=") && - defaultOnFunctionList.some((v) => linePrefix.endsWith(v + "=")) - ) { - let symbols = (await commands.executeCommand( - "vscode.executeDocumentSymbolProvider", - document.uri - )) as SymbolInformation[]; - symbols = symbols.filter((v) => v.kind === SymbolKind.Function); - return symbols.map((v) => new CompletionItem(v.name, CompletionItemKind.Function)); - } else if (linePrefix.endsWith("|")) { + return this.getCommonCompletion(document, linePrefix); + } + + private async getCommonCompletion(document: TextDocument, linePrefix: string): Promise { + if (linePrefix.endsWith("|")) { const foundElements = defaultElementList.reduce((p: string[], c: string) => { linePrefix.includes(`|${c}`) && p.push(c); return p; }, []); + // element type completion if (linePrefix.match(RE_LINE) && foundElements.length === 0) { - completionList = defaultElementList.map((v) => new CompletionItem(v, CompletionItemKind.Keyword)); - } else if (foundElements.length > 0) { + return defaultElementList.map((v) => new CompletionItem(v, CompletionItemKind.Keyword)); + } + // element property completion + if (linePrefix.match(RE_LINE) && foundElements.length > 0) { const latestElement = foundElements[foundElements.length - 1]; const properties = defaultElementProperties[latestElement as keyof typeof defaultElementProperties]; - const reducedPropertyList = Object.keys(properties).reduce((p: string[], c: string) => { - !linePrefix.includes(`|${c}`) && p.push(c); - return p; - }, []); - completionList = reducedPropertyList.map((v) => { - let completionItem = new CompletionItem(v, CompletionItemKind.Property); - completionItem.documentation = new MarkdownString(properties[v as keyof typeof properties]); - return completionItem; - }); + if (properties !== undefined) { + return Object.keys(properties) + .reduce((p: string[], c: string) => { + !linePrefix.includes(`|${c}`) && p.push(c); + return p; + }, []) + .map((v) => { + let completionItem = new CompletionItem(v, CompletionItemKind.Property); + completionItem.documentation = new MarkdownString(properties[v as keyof typeof properties]); + return completionItem; + }); + } } } - return completionList; + return []; + } + + private async getSymbols( + uri: Uri, + symbolKind: SymbolKind, + completionItemKind: CompletionItemKind + ): Promise { + let symbols = (await commands.executeCommand("vscode.executeDocumentSymbolProvider", uri)) as SymbolInformation[]; + return symbols.filter((v) => v.kind === symbolKind).map((v) => new CompletionItem(v.name, completionItemKind)); } } diff --git a/gui/src/gui/constant.ts b/gui/src/gui/constant.ts index 3900256..3debf17 100644 --- a/gui/src/gui/constant.ts +++ b/gui/src/gui/constant.ts @@ -2,21 +2,18 @@ import visualElements from "../assets/viselements.json"; import { getBlockElementList, getControlElementList, getElementList, getElementProperties, getOnFunctionList } from "./utils"; // object of all elements each with all of its properties -const defaultElementProperties = getElementProperties(visualElements); +export const defaultElementProperties = getElementProperties(visualElements); // Include control and block elements -const defaultElementList = getElementList(visualElements); +export const defaultElementList = getElementList(visualElements); -const defaultControlElementList = getControlElementList(visualElements); +export const defaultControlElementList = getControlElementList(visualElements); -const defaultBlockElementList = getBlockElementList(visualElements); +export const defaultBlockElementList = getBlockElementList(visualElements); -const defaultOnFunctionList = getOnFunctionList(defaultElementProperties); +export const defaultOnFunctionList = getOnFunctionList(defaultElementProperties); -export { - defaultElementProperties, - defaultElementList, - defaultControlElementList, - defaultBlockElementList, - defaultOnFunctionList, -}; +export enum LanguageId { + py = "python", + md = "markdown", +} diff --git a/gui/src/gui/diagnostics.ts b/gui/src/gui/diagnostics.ts index f51de4d..0f2b688 100644 --- a/gui/src/gui/diagnostics.ts +++ b/gui/src/gui/diagnostics.ts @@ -12,7 +12,7 @@ import { workspace, } from "vscode"; import { findBestMatch } from "string-similarity"; -import { defaultElementList, defaultBlockElementList, defaultElementProperties } from "./constant"; +import { defaultElementList, defaultBlockElementList, defaultElementProperties, LanguageId } from "./constant"; const CONTROL_RE = /<\|(.*?)\|>/; const OPENING_TAG_RE = /<([0-9a-zA-Z\_\.]*)\|((?:(?!\|>).)*)\s*$/; @@ -65,10 +65,10 @@ export const registerDiagnostics = (context: ExtensionContext): void => { const refreshDiagnostics = (doc: TextDocument, diagnosticCollection: DiagnosticCollection) => { let diagnostics: Diagnostic[] | undefined = undefined; - const uri = doc.uri.toString(); - if (uri.endsWith(".md") || doc.languageId === "markdown") { + const uri = doc.uri.fsPath; + if (uri.endsWith(".md") || doc.languageId === LanguageId.md) { diagnostics = getMdDiagnostics(doc); - } else if (uri.endsWith(".py") || doc.languageId === "python") { + } else if (uri.endsWith(".py") || doc.languageId === LanguageId.py) { diagnostics = getPyDiagnostics(doc); } diagnostics && diagnosticCollection.set(doc.uri, diagnostics); diff --git a/gui/src/gui/utils.ts b/gui/src/gui/utils.ts index 25460b5..571ca1e 100644 --- a/gui/src/gui/utils.ts +++ b/gui/src/gui/utils.ts @@ -1,4 +1,5 @@ import { DocumentFilter } from "vscode"; +import { LanguageId } from "./constant"; export const countChar = (str: string, char: string): number => { return str.split(char).length - 1; @@ -129,5 +130,5 @@ export const getOnFunctionList = (elementProperties: Record