Skip to content

Commit

Permalink
refactor completion (#104)
Browse files Browse the repository at this point in the history
* refactor completion

* make it even better per fred

* add language id type to completion

* per fred again
  • Loading branch information
dinhlongviolin1 authored Dec 21, 2022
1 parent 4ddc7ba commit 5086309
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 62 deletions.
126 changes: 82 additions & 44 deletions gui/src/gui/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,80 +15,118 @@ 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}/;

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,
token: CancellationToken
): Promise<CompletionItem[]> {
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<CompletionItem[]> {
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<CompletionItem[]> {
// 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<CompletionItem[]> {
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<CompletionItem[]> {
let symbols = (await commands.executeCommand("vscode.executeDocumentSymbolProvider", uri)) as SymbolInformation[];
return symbols.filter((v) => v.kind === symbolKind).map((v) => new CompletionItem(v.name, completionItemKind));
}
}
21 changes: 9 additions & 12 deletions gui/src/gui/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
8 changes: 4 additions & 4 deletions gui/src/gui/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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*$/;
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions gui/src/gui/utils.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -129,5 +130,5 @@ export const getOnFunctionList = (elementProperties: Record<string, Record<strin
return [...onFunctionList];
};

export const markdownDocumentFilter: DocumentFilter = { language: "markdown" };
export const pythonDocumentFilter: DocumentFilter = { language: "python" };
export const markdownDocumentFilter: DocumentFilter = { language: LanguageId.md };
export const pythonDocumentFilter: DocumentFilter = { language: LanguageId.py };

0 comments on commit 5086309

Please sign in to comment.