diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index d95fd5a..7b34546 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -153,6 +153,24 @@ describe("expressions", () => { ]); }); + it("multiple regions - first region", async () => { + const input = "run-name: test-${{ git| == 1 }}-${{ github.event }}"; + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + expect(result.map(x => x.label)).toEqual([ + "github", + "inputs", + "vars", + "contains", + "endsWith", + "format", + "fromJson", + "join", + "startsWith", + "toJson" + ]); + }); + it("multiple regions", async () => { const input = "run-name: test-${{ github }}-${{ | }}"; const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index 54942fb..5ba7678 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -1,4 +1,4 @@ -import {DescriptionDictionary, complete as completeExpression} from "@github/actions-expressions"; +import {complete as completeExpression, DescriptionDictionary} from "@github/actions-expressions"; import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; import { convertWorkflowTemplate, @@ -8,8 +8,6 @@ import { 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"; -import {StringDefinition} from "@github/actions-workflow-parser/templates/schema/string-definition"; import {OPEN_EXPRESSION} from "@github/actions-workflow-parser/templates/template-constants"; import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/index"; import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token"; @@ -26,11 +24,12 @@ import {nullTrace} from "./nulltrace"; import {findToken} from "./utils/find-token"; import {guessIndentation} from "./utils/indentation-guesser"; import {mapRange} from "./utils/range"; +import {getRelCharOffset} from "./utils/rel-char-pos"; import {transform} from "./utils/transform"; +import {isStringExpression} from "./utils/type-guards"; import {Value, ValueProviderConfig} from "./value-providers/config"; import {defaultValueProviders} from "./value-providers/default"; import {definitionValues} from "./value-providers/definition"; -import {TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range"; export function getExpressionInput(input: string, pos: number): string { // Find start marker around the cursor position @@ -79,7 +78,7 @@ export async function complete( // expression nodes for invalid expressions and during editing expressions are invalid most of the time. if (token) { const isStringExpressionToken = isStringExpression(token); - const isBasicExpressionToken = isBasicExpression(token) && token.isExpression; + const isBasicExpressionToken = isBasicExpression(token); if (isStringExpressionToken || isBasicExpressionToken) { const allowedContext = token.definitionInfo?.allowedContext || []; @@ -93,12 +92,14 @@ export async function complete( const indentString = " ".repeat(indentation.tabSize); const values = await getValues(token, keyToken, parent, valueProviderConfig, workflowContext, indentString); + let replaceRange: Range | undefined; if (token?.range) { replaceRange = mapRange(token.range); } else if (!token) { // Not a valid token, create a range from the current position const line = newDoc.getText({start: {line: position.line, character: 0}, end: position}); + // Get the length of the current word const val = line.match(/[\w_-]*$/)?.[0].length || 0; replaceRange = Range.create({line: position.line, character: position.character - val}, position); @@ -207,21 +208,21 @@ function getExpressionCompletionItems( ): CompletionItem[] { let expressionInput = ""; let currentInput = ""; - let relCharPos: number = 0; + let relCharOffset: number = 0; if (isBasicExpression(token)) { expressionInput = currentInput = token.expression; - relCharPos = getRelCharPos(token.range!, expressionInput, pos); + relCharOffset = getRelCharOffset(token.range!, expressionInput, pos); } else { const stringToken = token.assertString("Expected string token for expression completion"); currentInput = stringToken.source || stringToken.value; - relCharPos = getRelCharPos(stringToken.range!, currentInput, pos); - expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim(); + relCharOffset = getRelCharOffset(stringToken.range!, currentInput, pos); + expressionInput = (getExpressionInput(currentInput, relCharOffset) || "").trim(); } try { return completeExpression(expressionInput, context, [], validatorFunctions).map(item => - mapExpressionCompletionItem(item, currentInput[relCharPos]) + mapExpressionCompletionItem(item, currentInput[relCharOffset]) ); } catch (e: any) { error(`Error while completing expression: '${e?.message || ""}'`); @@ -252,23 +253,3 @@ function mapExpressionCompletionItem(item: ExpressionCompletionItem, charAfterPo kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable }; } - -function getRelCharPos(tokenRange: TokenRange, currentInput: string, pos: Position): number { - // Transform the overall position into a node relative position - const range = mapRange(tokenRange); - if (range.start.line !== range.end.line) { - const lines = currentInput.split("\n"); - const lineDiff = pos.line - range.start.line - 1; - const linesBeforeCusor = lines.slice(0, lineDiff); - return linesBeforeCusor.join("\n").length + pos.character + 1; - } else { - return pos.character - range.start.character; - } -} - -function isStringExpression(token: TemplateToken): boolean { - const isExpression = - token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression; - const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0; - return isExpression || containsExpression; -}