diff --git a/src/languages/index.ts b/src/languages/index.ts index 3396da1317..3732b6779d 100644 --- a/src/languages/index.ts +++ b/src/languages/index.ts @@ -59,19 +59,38 @@ function matcherIncludeSiblings(matcher: NodeMatcher): NodeMatcher { selection: SelectionWithEditor, node: SyntaxNode ): NodeMatcherValue[] | null => { - const matches = matcher(selection, node); + let matches = matcher(selection, node); if (matches == null) { return null; } - return matches - .flatMap((match) => - match.node.parent!.namedChildren.flatMap((sibling) => - matcher( - selectionWithEditorFromRange(selection, match.selection.selection), - sibling - ) - ) + matches = matches.flatMap((match) => + iterateNearestIterableAncestor( + match.node, + selectionWithEditorFromRange(selection, match.selection.selection), + matcher ) - .filter((match) => match != null) as NodeMatcherValue[]; + ) as NodeMatcherValue[]; + if (matches.length > 0) { + return matches; + } + return null; }; } + +function iterateNearestIterableAncestor( + node: SyntaxNode, + selection: SelectionWithEditor, + nodeMatcher: NodeMatcher +) { + let parent: SyntaxNode | null = node.parent; + while (parent != null) { + const matches = parent!.namedChildren + .flatMap((sibling) => nodeMatcher(selection, sibling)) + .filter((match) => match != null) as NodeMatcherValue[]; + if (matches.length > 0) { + return matches; + } + parent = parent.parent; + } + return []; +} diff --git a/src/processTargets.ts b/src/processTargets.ts index 17fa27a03d..dd85741f85 100644 --- a/src/processTargets.ts +++ b/src/processTargets.ts @@ -297,11 +297,11 @@ function transformSelection( modifier.scopeType, modifier.includeSiblings ?? false ); - let node: SyntaxNode | null = context.getNodeAtLocation( + const node: SyntaxNode | null = context.getNodeAtLocation( new Location(selection.editor.document.uri, selection.selection) ); - let result = findNearestContainingAncestorNode( + const result = findNearestContainingAncestorNode( node, nodeMatcher, selection @@ -418,18 +418,17 @@ function transformSelection( case "surroundingPair": { - let node: SyntaxNode | null = context.getNodeAtLocation( + const node: SyntaxNode | null = context.getNodeAtLocation( new vscode.Location( selection.editor.document.uri, selection.selection ) ); - const nodeMatcher = createSurroundingPairMatcher( modifier.delimiter, modifier.delimitersOnly ); - let result = findNearestContainingAncestorNode( + const result = findNearestContainingAncestorNode( node, nodeMatcher, selection diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgAir.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgAir.yml new file mode 100644 index 0000000000..e348983f89 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgAir.yml @@ -0,0 +1,31 @@ +spokenForm: take every arg air +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + extraArgs: [] +marks: + default.a: + start: {line: 0, character: 8} + end: {line: 0, character: 11} +initialState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +finalState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 11} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 18} + thatMark: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 11} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 18} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: a}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgBat.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgBat.yml new file mode 100644 index 0000000000..bfd025e48f --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgBat.yml @@ -0,0 +1,31 @@ +spokenForm: take every arg bat +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true} + mark: {type: decoratedSymbol, symbolColor: default, character: b} + extraArgs: [] +marks: + default.b: + start: {line: 0, character: 21} + end: {line: 0, character: 27} +initialState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +finalState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 19} + - anchor: {line: 0, character: 21} + active: {line: 0, character: 27} + thatMark: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 19} + - anchor: {line: 0, character: 21} + active: {line: 0, character: 27} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgRam.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgRam.yml new file mode 100644 index 0000000000..da6e85a756 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryArgRam.yml @@ -0,0 +1,31 @@ +spokenForm: take every arg ram +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true} + mark: {type: decoratedSymbol, symbolColor: default, character: r} + extraArgs: [] +marks: + default.r: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +initialState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +finalState: + documentContents: foo(bar(baz, bongo), bazman) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 19} + - anchor: {line: 0, character: 21} + active: {line: 0, character: 27} + thatMark: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 19} + - anchor: {line: 0, character: 21} + active: {line: 0, character: 27} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: r}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: true}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryItem5.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryItem5.yml new file mode 100644 index 0000000000..018a4bd083 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryItem5.yml @@ -0,0 +1,45 @@ +spokenForm: take every item +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: true} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 17} +finalState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 17} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 17} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 17} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 17} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: true}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryKey.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryKey.yml new file mode 100644 index 0000000000..cf84c6e064 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryKey.yml @@ -0,0 +1,35 @@ +spokenForm: take every key +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} +finalState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 8} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 8} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 8} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 8} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeEveryValue.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryValue.yml new file mode 100644 index 0000000000..2a0d338a87 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeEveryValue.yml @@ -0,0 +1,35 @@ +spokenForm: take every value +languageId: typescript +command: + actionName: setSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: true} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} +finalState: + documentContents: |- + const value = { + key1: "hello", + key2: "there", + }; + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 17} + thatMark: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 17} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 17} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: value, includeSiblings: true}, insideOutsideType: inside}]