Skip to content

Commit 65fab51

Browse files
Support jsx tag scopes using tree-sitter queries (#1508)
- Fixes #1478 - Depends on #1507 ## Checklist - [x] Support "every name" while we're here - [x] Arg lists - [x] Functions in class - [x] Assignment statements in function body - [x] Assignment statements at top level - [x] Properties in class - [x] Members of interface - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [ ] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [ ] I have not broken the cheatsheet --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent f759734 commit 65fab51

25 files changed

+644
-76
lines changed

packages/cursorless-engine/src/languages/typescript.ts

+2-57
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { Selection, SimpleScopeTypeType, TextEditor } from "@cursorless/common";
1+
import { SimpleScopeTypeType } from "@cursorless/common";
22
import type { SyntaxNode } from "web-tree-sitter";
33
import {
44
NodeMatcher,
55
NodeMatcherAlternative,
66
SelectionWithEditor,
77
} from "../typings/Types";
8-
import { patternFinder, typedNodeFinder } from "../util/nodeFinders";
8+
import { patternFinder } from "../util/nodeFinders";
99
import {
1010
argumentMatcher,
1111
cascadingMatcher,
@@ -20,12 +20,10 @@ import {
2020
extendForwardPastOptional,
2121
getNodeInternalRange,
2222
getNodeRange,
23-
jsxFragmentExtractor,
2423
pairSelectionExtractor,
2524
selectWithLeadingDelimiter,
2625
simpleSelectionExtractor,
2726
unwrapSelectionExtractor,
28-
xmlElementExtractor,
2927
} from "../util/nodeSelectors";
3028
import { branchMatcher } from "./branchMatcher";
3129
import { elseExtractor, elseIfExtractor } from "./elseIfExtractor";
@@ -71,42 +69,6 @@ const STATEMENT_TYPES = [
7169
"with_statement",
7270
];
7371

74-
/** Handles jsx fragment start or end tag, eg the `<>` in `<>foo</>` **/
75-
function getJsxFragmentTag(isStartTag: boolean): NodeMatcher {
76-
return matcher(
77-
typedNodeFinder("jsx_fragment"),
78-
(editor: TextEditor, node: SyntaxNode) => {
79-
const [start, end] = isStartTag
80-
? [node.children[0], node.children[1]]
81-
: [node.children.at(-3)!, node.children.at(-1)!];
82-
return {
83-
selection: new Selection(
84-
start.startPosition.row,
85-
start.startPosition.column,
86-
end.endPosition.row,
87-
end.endPosition.column,
88-
),
89-
context: {},
90-
};
91-
},
92-
);
93-
}
94-
95-
const getStartTag = cascadingMatcher(
96-
patternMatcher("jsx_element.jsx_opening_element!"),
97-
getJsxFragmentTag(true),
98-
);
99-
const getEndTag = cascadingMatcher(
100-
patternMatcher("jsx_element.jsx_closing_element!"),
101-
getJsxFragmentTag(false),
102-
);
103-
104-
const getTags = (selection: SelectionWithEditor, node: SyntaxNode) => {
105-
const startTag = getStartTag(selection, node);
106-
const endTag = getEndTag(selection, node);
107-
return startTag != null && endTag != null ? startTag.concat(endTag) : null;
108-
};
109-
11072
function typeMatcher(): NodeMatcher {
11173
const delimiterSelector = selectWithLeadingDelimiter(":");
11274
return function (selection: SelectionWithEditor, node: SyntaxNode) {
@@ -214,13 +176,6 @@ const nodeMatchers: Partial<
214176
),
215177
ifStatement: "if_statement",
216178
anonymousFunction: ["arrow_function", "function"],
217-
name: [
218-
"*[name]",
219-
"optional_parameter.identifier!",
220-
"required_parameter.identifier!",
221-
"augmented_assignment_expression[left]",
222-
"assignment_expression[left]",
223-
],
224179
comment: "comment",
225180
regularExpression: "regex",
226181
className: ["class_declaration[name]", "class[name]"],
@@ -337,16 +292,6 @@ const nodeMatchers: Partial<
337292
argumentOrParameter: argumentMatcher("formal_parameters", "arguments"),
338293
// XML, JSX
339294
attribute: ["jsx_attribute"],
340-
xmlElement: cascadingMatcher(
341-
matcher(
342-
typedNodeFinder("jsx_element", "jsx_self_closing_element"),
343-
xmlElementExtractor,
344-
),
345-
matcher(typedNodeFinder("jsx_fragment"), jsxFragmentExtractor),
346-
),
347-
xmlBothTags: getTags,
348-
xmlStartTag: getStartTag,
349-
xmlEndTag: getEndTag,
350295
};
351296

352297
export const patternMatchers = createPatternMatchers(nodeMatchers);

packages/cursorless-engine/src/util/nodeSelectors.ts

-19
Original file line numberDiff line numberDiff line change
@@ -427,25 +427,6 @@ export function xmlElementExtractor(
427427
return selection;
428428
}
429429

430-
export function jsxFragmentExtractor(
431-
editor: TextEditor,
432-
node: SyntaxNode,
433-
): SelectionWithContext {
434-
const selection = simpleSelectionExtractor(editor, node);
435-
436-
// Interior range for an element is found by excluding the start and end nodes.
437-
const startPosition = node.children[1].endPosition;
438-
const endPosition = node.children.at(-3)!.startPosition;
439-
selection.context.interiorRange = new Range(
440-
startPosition.row,
441-
startPosition.column,
442-
endPosition.row,
443-
endPosition.column,
444-
);
445-
446-
return selection;
447-
}
448-
449430
export function getInsertionDelimiter(
450431
editor: TextEditor,
451432
leadingDelimiterRange: Range | undefined,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every element
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlElement}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<aaa>
15+
<bbb>ccc</bbb>
16+
<bbb>ddd</bbb>
17+
</aaa>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<aaa>
25+
26+
27+
</aaa>
28+
selections:
29+
- anchor: {line: 1, character: 4}
30+
active: {line: 1, character: 4}
31+
- anchor: {line: 2, character: 4}
32+
active: {line: 2, character: 4}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every element
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlElement}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<>
15+
<bbb>ccc</bbb>
16+
<bbb>ddd</bbb>
17+
</>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<>
25+
26+
27+
</>
28+
selections:
29+
- anchor: {line: 1, character: 4}
30+
active: {line: 1, character: 4}
31+
- anchor: {line: 2, character: 4}
32+
active: {line: 2, character: 4}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every element
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlElement}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<>
15+
<>ccc</>
16+
<>ddd</>
17+
</>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<>
25+
26+
27+
</>
28+
selections:
29+
- anchor: {line: 1, character: 4}
30+
active: {line: 1, character: 4}
31+
- anchor: {line: 2, character: 4}
32+
active: {line: 2, character: 4}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every element
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlElement}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<aaa>
15+
<>ccc</>
16+
<>ddd</>
17+
</aaa>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<aaa>
25+
26+
27+
</aaa>
28+
selections:
29+
- anchor: {line: 1, character: 4}
30+
active: {line: 1, character: 4}
31+
- anchor: {line: 2, character: 4}
32+
active: {line: 2, character: 4}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every end tag
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlEndTag}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<>
15+
<>ccc</>
16+
<>ddd</>
17+
</>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<>
25+
<>ccc
26+
<>ddd
27+
</>
28+
selections:
29+
- anchor: {line: 1, character: 9}
30+
active: {line: 1, character: 9}
31+
- anchor: {line: 2, character: 9}
32+
active: {line: 2, character: 9}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescriptreact
2+
command:
3+
version: 5
4+
spokenForm: clear every end tag
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: xmlEndTag}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
<aaa>
15+
<bbb>ccc</bbb>
16+
<bbb>ddd</bbb>
17+
</aaa>
18+
selections:
19+
- anchor: {line: 1, character: 4}
20+
active: {line: 1, character: 4}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
<aaa>
25+
<bbb>ccc
26+
<bbb>ddd
27+
</aaa>
28+
selections:
29+
- anchor: {line: 1, character: 12}
30+
active: {line: 1, character: 12}
31+
- anchor: {line: 2, character: 12}
32+
active: {line: 2, character: 12}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
languageId: typescript
2+
command:
3+
version: 5
4+
spokenForm: clear every name
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: name}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
const aaa = "bbb";
15+
const ccc = "ddd";
16+
selections:
17+
- anchor: {line: 1, character: 18}
18+
active: {line: 1, character: 18}
19+
marks: {}
20+
finalState:
21+
documentContents: |-
22+
const = "bbb";
23+
const = "ddd";
24+
selections:
25+
- anchor: {line: 0, character: 6}
26+
active: {line: 0, character: 6}
27+
- anchor: {line: 1, character: 6}
28+
active: {line: 1, character: 6}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescript
2+
command:
3+
version: 5
4+
spokenForm: clear every name
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: everyScope
10+
scopeType: {type: name}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: |-
14+
function eee() {
15+
const aaa = "bbb";
16+
const ccc = "ddd";
17+
}
18+
selections:
19+
- anchor: {line: 2, character: 22}
20+
active: {line: 2, character: 22}
21+
marks: {}
22+
finalState:
23+
documentContents: |-
24+
function eee() {
25+
const = "bbb";
26+
const = "ddd";
27+
}
28+
selections:
29+
- anchor: {line: 1, character: 10}
30+
active: {line: 1, character: 10}
31+
- anchor: {line: 2, character: 10}
32+
active: {line: 2, character: 10}

0 commit comments

Comments
 (0)