From cbae97efe4c4b7a52206c6db0653aa83617dafc0 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Sun, 31 Jul 2022 23:17:32 +0200 Subject: [PATCH] Add basic completion service Still WIP --- .../src/languageService.ts | 5 +- .../src/schema/schemaNodeResolver.ts | 1 - .../src/services/completionService.ts | 82 +++++++++++++++++++ .../tests/unit/schema.test.ts | 2 +- 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 server/gx-workflow-ls-format2/src/services/completionService.ts diff --git a/server/gx-workflow-ls-format2/src/languageService.ts b/server/gx-workflow-ls-format2/src/languageService.ts index 8bfd6ca..a72f99b 100644 --- a/server/gx-workflow-ls-format2/src/languageService.ts +++ b/server/gx-workflow-ls-format2/src/languageService.ts @@ -13,6 +13,7 @@ import { import { LanguageService, getLanguageService } from "@gxwf/yaml-language-service/src/yamlLanguageService"; import { GxFormat2WorkflowDocument } from "./gxFormat2WorkflowDocument"; import { GalaxyWorkflowFormat2SchemaLoader } from "./schema"; +import { GxFormat2CompletionService } from "./services/completionService"; import { GxFormat2HoverService } from "./services/hoverService"; /** @@ -23,11 +24,13 @@ export class GxFormat2WorkflowLanguageService extends WorkflowLanguageService { private _yamlLanguageService: LanguageService; private _schemaLoader: GalaxyWorkflowFormat2SchemaLoader; private _hoverService: GxFormat2HoverService; + private _completionService: GxFormat2CompletionService; constructor() { super(); this._schemaLoader = new GalaxyWorkflowFormat2SchemaLoader(); this._yamlLanguageService = getLanguageService(); this._hoverService = new GxFormat2HoverService(this._schemaLoader.nodeResolver); + this._completionService = new GxFormat2CompletionService(this._schemaLoader.nodeResolver); } public override parseWorkflowDocument(document: TextDocument): WorkflowDocument { @@ -47,7 +50,7 @@ export class GxFormat2WorkflowLanguageService extends WorkflowLanguageService { workflowDocument: WorkflowDocument, position: Position ): Promise { - return null; + return this._completionService.doComplete(workflowDocument.textDocument, position, workflowDocument.nodeManager); } protected override async doValidation(workflowDocument: WorkflowDocument): Promise { diff --git a/server/gx-workflow-ls-format2/src/schema/schemaNodeResolver.ts b/server/gx-workflow-ls-format2/src/schema/schemaNodeResolver.ts index 5936b97..b20ba44 100644 --- a/server/gx-workflow-ls-format2/src/schema/schemaNodeResolver.ts +++ b/server/gx-workflow-ls-format2/src/schema/schemaNodeResolver.ts @@ -18,7 +18,6 @@ export class SchemaNodeResolver { return this.getSchemaNodeForSegment(parentNode.typeRef); } } - return schemaNodeFound; } diff --git a/server/gx-workflow-ls-format2/src/services/completionService.ts b/server/gx-workflow-ls-format2/src/services/completionService.ts new file mode 100644 index 0000000..912675d --- /dev/null +++ b/server/gx-workflow-ls-format2/src/services/completionService.ts @@ -0,0 +1,82 @@ +import { ASTNodeManager } from "@gxwf/server-common/src/ast/nodeManager"; +import { ASTNode } from "@gxwf/server-common/src/ast/types"; +import { + CompletionItem, + CompletionItemKind, + CompletionList, + Position, + TextDocument, +} from "@gxwf/server-common/src/languageTypes"; +import { TextBuffer } from "@gxwf/yaml-language-service/src/utils/textBuffer"; +import { SchemaNode, SchemaNodeResolver } from "../schema"; + +export class GxFormat2CompletionService { + constructor(protected readonly schemaNodeResolver: SchemaNodeResolver) {} + + public doComplete( + textDocument: TextDocument, + position: Position, + nodeManager: ASTNodeManager + ): Promise { + const result: CompletionList = { + items: [], + isIncomplete: false, + }; + + const textBuffer = new TextBuffer(textDocument); + const text = textBuffer.getText(); + const offset = textBuffer.getOffsetAt(position); + const node = nodeManager.getNodeFromOffset(offset); + if (!node) { + return Promise.resolve(result); + } + if (text.charAt(offset - 1) === ":") { + return Promise.resolve(result); + } + + DEBUG_printNodeName(node); + + const existing = nodeManager.getDeclaredPropertyNames(node); + if (nodeManager.isRoot(node)) { + result.items = this.getProposedItems(this.schemaNodeResolver.rootNode, existing); + return Promise.resolve(result); + } + const nodePath = nodeManager.getPathFromNode(node); + const schemaNode = this.schemaNodeResolver.resolveSchemaContext(nodePath); + if (schemaNode) { + result.items = this.getProposedItems(schemaNode, existing); + } + return Promise.resolve(result); + } + + private getProposedItems(schemaNode: SchemaNode, exclude: Set): CompletionItem[] { + const result: CompletionItem[] = []; + schemaNode.children.forEach((child) => { + if (exclude.has(child.name)) return; + const item: CompletionItem = { + label: child.name, + documentation: child.documentation, + sortText: `_${child.name}`, + kind: CompletionItemKind.Field, + insertText: `${child.name}: `, + }; + result.push(item); + }); + return result; + } +} + +function DEBUG_printNodeName(node: ASTNode): void { + let nodeName = "_root_"; + if (node?.type === "property") { + nodeName = node.keyNode.value; + console.debug("COMPLETION NODE PROPERTY", nodeName); + } else if (node?.type === "object") { + console.debug(`COMPLETION NODE OBJECT:`); + node.properties.forEach((p) => { + console.debug(` ${p.keyNode.value}`); + }); + } else { + console.debug("UNKNOWN"); + } +} diff --git a/server/gx-workflow-ls-format2/tests/unit/schema.test.ts b/server/gx-workflow-ls-format2/tests/unit/schema.test.ts index 66f32aa..3834f83 100644 --- a/server/gx-workflow-ls-format2/tests/unit/schema.test.ts +++ b/server/gx-workflow-ls-format2/tests/unit/schema.test.ts @@ -16,7 +16,7 @@ describe("Gxformat2 Schema Handling", () => { describe("SchemaNodeResolver", () => { let nodeResolver: SchemaNodeResolver; beforeAll(() => { - nodeResolver = new SchemaNodeResolver(schemaLoader.definitions); + nodeResolver = schemaLoader.nodeResolver; }); describe("resolveSchemaContext", () => { it.each([