diff --git a/actions-languageservice/package.json b/actions-languageservice/package.json index bba26e8..586ec76 100644 --- a/actions-languageservice/package.json +++ b/actions-languageservice/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@github/actions-workflow-parser": "*", + "@github/actions-expressions": "*", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -58,4 +59,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.8.4" } -} +} \ No newline at end of file diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index fb2df57..6b5e41b 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -1,4 +1,5 @@ import {data} from "@github/actions-expressions"; +import {CompletionItemKind} from "vscode-languageserver-types"; import {complete, getExpressionInput} from "./complete"; import {ContextProviderConfig} from "./context-providers/config"; import {registerLogger} from "./log"; @@ -672,4 +673,53 @@ jobs: expect(result.map(x => x.label)).toEqual(["color"]); }); }); + + it("context completion items include kind and insert text", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo \${{ | }}.txt + `; + + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + // Built-in function + const toJSON = result.find(x => x.label === "toJson"); + expect(toJSON).toBeDefined(); + expect(toJSON!.kind).toBe(CompletionItemKind.Function); + expect(toJSON!.insertText).toBe("toJson()"); + + // Function from context + const hashFiles = result.find(x => x.label === "hashFiles"); + expect(hashFiles).toBeDefined(); + expect(hashFiles!.kind).toBe(CompletionItemKind.Function); + expect(hashFiles!.insertText).toBe("hashFiles()"); + + // Not a function + const github = result.find(x => x.label === "github"); + expect(github).toBeDefined(); + expect(github!.kind).toBe(CompletionItemKind.Variable); + expect(github!.insertText).toBeUndefined(); + }); + + it("function parentheses are not inserted when parentheses already exist", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo \${{ toJS|(github.event) }} + `; + + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + expect(result.find(x => x.label === "toJson")!.insertText).toBe("toJson"); + }); }); diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index f2d89f1..0477b19 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -1,4 +1,5 @@ import {complete as completeExpression} from "@github/actions-expressions"; +import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; import {convertWorkflowTemplate, isSequence, isString, parseWorkflow} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {DefinitionType} from "@github/actions-workflow-parser/templates/schema/definition-type"; @@ -9,7 +10,7 @@ import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/map import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types"; import {File} from "@github/actions-workflow-parser/workflows/file"; import {Position, TextDocument} from "vscode-languageserver-textdocument"; -import {CompletionItem, CompletionItemTag, Range, TextEdit} from "vscode-languageserver-types"; +import {CompletionItem, CompletionItemKind, CompletionItemTag, Range, TextEdit} from "vscode-languageserver-types"; import {ContextProviderConfig} from "./context-providers/config"; import {getContext, Mode} from "./context-providers/default"; import {getWorkflowContext, WorkflowContext} from "./context/workflow-context"; @@ -87,7 +88,9 @@ export async function complete( const allowedContext = token.definitionInfo?.allowedContext || []; const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion); - return completeExpression(expressionInput, context, []); + return completeExpression(expressionInput, context, []).map(item => + mapExpressionCompletionItem(item, currentInput[relCharPos]) + ); } } @@ -192,3 +195,17 @@ function filterAndSortCompletionOptions(options: Value[], existingValues?: Set a.label.localeCompare(b.label)); return options; } + +function mapExpressionCompletionItem(item: ExpressionCompletionItem, charAfterPos: string): CompletionItem { + let insertText: string | undefined; + // Insert parentheses if the cursor is after a function + // and the function does not have any parantheses already + if (item.function) { + insertText = charAfterPos === "(" ? item.label : item.label + "()"; + } + return { + label: item.label, + insertText: insertText, + kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable + }; +} diff --git a/package-lock.json b/package-lock.json index f48743b..3af4e69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "version": "0.1.57", "license": "MIT", "dependencies": { + "@github/actions-expressions": "*", "@github/actions-workflow-parser": "*", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", @@ -683,9 +684,9 @@ "dev": true }, "node_modules/@github/actions-expressions": { - "version": "0.0.7", - "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.7/deed2be11b9f74791730303b6e33f9aca701d6a7", - "integrity": "sha512-PG8dQUafOckk/ny5O1wcUcfml5IZM8REoi7WBBY1HfpnRR7mL7gqTczQEmQWNTpoHb9f1VQK47y2hBZTOg9Y7g==", + "version": "0.0.8", + "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.8/055f455fcb0ed907f52c349b9385728aeed38f9a", + "integrity": "sha512-SPuGfnjgKAbMzJNCk4sZPAK2bxHlRXDJCk6vrwIi9VJL16Eh1YyiyM4fU9kV5EXEE5AV9aPFlZAPXu6t0mmBNw==", "license": "MIT", "engines": { "node": ">= 16" @@ -13500,9 +13501,9 @@ "dev": true }, "@github/actions-expressions": { - "version": "0.0.7", - "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.7/deed2be11b9f74791730303b6e33f9aca701d6a7", - "integrity": "sha512-PG8dQUafOckk/ny5O1wcUcfml5IZM8REoi7WBBY1HfpnRR7mL7gqTczQEmQWNTpoHb9f1VQK47y2hBZTOg9Y7g==" + "version": "0.0.8", + "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.8/055f455fcb0ed907f52c349b9385728aeed38f9a", + "integrity": "sha512-SPuGfnjgKAbMzJNCk4sZPAK2bxHlRXDJCk6vrwIi9VJL16Eh1YyiyM4fU9kV5EXEE5AV9aPFlZAPXu6t0mmBNw==" }, "@github/actions-languageserver": { "version": "file:actions-languageserver", @@ -13524,6 +13525,7 @@ "@github/actions-languageservice": { "version": "file:actions-languageservice", "requires": { + "@github/actions-expressions": "*", "@github/actions-workflow-parser": "*", "@types/jest": "^29.0.3", "jest": "^29.0.3",