diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryCapture.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryCapture.ts index f92fde3098..c4c8ce5797 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryCapture.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/QueryCapture.ts @@ -37,7 +37,7 @@ export interface QueryCapture { readonly allowMultiple: boolean; /** The insertion delimiter to use if any */ - readonly insertionDelimiter: string | undefined; + readonly insertionDelimiters: string[] | undefined; } /** @@ -63,7 +63,7 @@ export interface MutableQueryCapture extends QueryCapture { readonly document: TextDocument; range: Range; allowMultiple: boolean; - insertionDelimiter: string | undefined; + insertionDelimiters: string[] | undefined; } /** diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts index 3ecd56b1a5..0d52d7d71f 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts @@ -80,7 +80,7 @@ export class TreeSitterQuery { node, document, range: getNodeRange(node), - insertionDelimiter: undefined, + insertionDelimiters: undefined, allowMultiple: false, })), }), @@ -114,9 +114,9 @@ export class TreeSitterQuery { .map(({ range }) => range) .reduce((accumulator, range) => range.union(accumulator)), allowMultiple: captures.some((capture) => capture.allowMultiple), - insertionDelimiter: captures.find( - (capture) => capture.insertionDelimiter != null, - )?.insertionDelimiter, + insertionDelimiters: captures.find( + (capture) => capture.insertionDelimiters != null, + )?.insertionDelimiters, }; }); diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.test.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.test.ts index 3d4afa5729..d35256adaa 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.test.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/checkCaptureStartEnd.test.ts @@ -5,7 +5,7 @@ import assert from "assert"; interface TestCase { name: string; - captures: Omit[]; + captures: Omit[]; isValid: boolean; expectedErrorMessageIds: string[]; } @@ -192,7 +192,7 @@ suite("checkCaptureStartEnd", () => { testCase.captures.map((capture) => ({ ...capture, allowMultiple: false, - insertionDelimiter: undefined, + insertionDelimiters: undefined, })), messages, ); diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index a5077ffbde..f12e94fa5b 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -205,10 +205,14 @@ class Log extends QueryPredicateOperator { */ class InsertionDelimiter extends QueryPredicateOperator { name = "insertion-delimiter!" as const; - schema = z.tuple([q.node, q.string]); + schema = z.union([ + z.tuple([q.node, q.string]), + z.tuple([q.node, q.string, q.string]), + z.tuple([q.node, q.string, q.string, q.string]), + ]); - run(nodeInfo: MutableQueryCapture, insertionDelimiter: string) { - nodeInfo.insertionDelimiter = insertionDelimiter; + run(nodeInfo: MutableQueryCapture, ...insertionDelimiters: string[]) { + nodeInfo.insertionDelimiters = insertionDelimiters; return true; } diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/rewriteStartOfEndOf.test.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/rewriteStartOfEndOf.test.ts index cc8e99c789..a22c92b07e 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/rewriteStartOfEndOf.test.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/rewriteStartOfEndOf.test.ts @@ -54,7 +54,7 @@ function fillOutCapture(capture: NameRange): MutableQueryCapture { return { ...capture, allowMultiple: false, - insertionDelimiter: undefined, + insertionDelimiters: undefined, document: null as unknown as TextDocument, node: null as unknown as SyntaxNode, }; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts index d05940133f..0bb44361ed 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts @@ -48,7 +48,7 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { return undefined; } - const { range: contentRange, allowMultiple, insertionDelimiter } = capture; + const { range: contentRange, allowMultiple, insertionDelimiters } = capture; const domain = getRelatedRange(match, scopeTypeType, "domain", true) ?? contentRange; @@ -70,6 +70,8 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { true, ); + const delimiter = getInsertionDelimiter(editor, match, insertionDelimiters); + return { editor, domain, @@ -84,13 +86,39 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { leadingDelimiterRange, trailingDelimiterRange, interiorRange, - delimiter: insertionDelimiter, + delimiter, }), ], }; } } +function getInsertionDelimiter( + editor: TextEditor, + match: QueryMatch, + insertionDelimiters?: string[], +): string | undefined { + if (insertionDelimiters == null) { + return undefined; + } + + for (const delimiter of insertionDelimiters) { + if (!delimiter.startsWith("@")) { + return delimiter; + } + + const capture = findCaptureByName(match, delimiter.slice(1)); + if (capture != null) { + const { range } = capture; + if (!range.isEmpty) { + return editor.document.getText(range); + } + } + } + + return undefined; +} + function dropEmptyRange(range?: Range) { return range != null && !range.isEmpty ? range : undefined; }