Skip to content

Commit

Permalink
Support jsx tag scopes using tree-sitter queries (#1508)
Browse files Browse the repository at this point in the history
- 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>
  • Loading branch information
pokey and pre-commit-ci-lite[bot] committed Jun 6, 2023
1 parent f759734 commit 65fab51
Show file tree
Hide file tree
Showing 25 changed files with 644 additions and 76 deletions.
59 changes: 2 additions & 57 deletions packages/cursorless-engine/src/languages/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Selection, SimpleScopeTypeType, TextEditor } from "@cursorless/common";
import { SimpleScopeTypeType } from "@cursorless/common";
import type { SyntaxNode } from "web-tree-sitter";
import {
NodeMatcher,
NodeMatcherAlternative,
SelectionWithEditor,
} from "../typings/Types";
import { patternFinder, typedNodeFinder } from "../util/nodeFinders";
import { patternFinder } from "../util/nodeFinders";
import {
argumentMatcher,
cascadingMatcher,
Expand All @@ -20,12 +20,10 @@ import {
extendForwardPastOptional,
getNodeInternalRange,
getNodeRange,
jsxFragmentExtractor,
pairSelectionExtractor,
selectWithLeadingDelimiter,
simpleSelectionExtractor,
unwrapSelectionExtractor,
xmlElementExtractor,
} from "../util/nodeSelectors";
import { branchMatcher } from "./branchMatcher";
import { elseExtractor, elseIfExtractor } from "./elseIfExtractor";
Expand Down Expand Up @@ -71,42 +69,6 @@ const STATEMENT_TYPES = [
"with_statement",
];

/** Handles jsx fragment start or end tag, eg the `<>` in `<>foo</>` **/
function getJsxFragmentTag(isStartTag: boolean): NodeMatcher {
return matcher(
typedNodeFinder("jsx_fragment"),
(editor: TextEditor, node: SyntaxNode) => {
const [start, end] = isStartTag
? [node.children[0], node.children[1]]
: [node.children.at(-3)!, node.children.at(-1)!];
return {
selection: new Selection(
start.startPosition.row,
start.startPosition.column,
end.endPosition.row,
end.endPosition.column,
),
context: {},
};
},
);
}

const getStartTag = cascadingMatcher(
patternMatcher("jsx_element.jsx_opening_element!"),
getJsxFragmentTag(true),
);
const getEndTag = cascadingMatcher(
patternMatcher("jsx_element.jsx_closing_element!"),
getJsxFragmentTag(false),
);

const getTags = (selection: SelectionWithEditor, node: SyntaxNode) => {
const startTag = getStartTag(selection, node);
const endTag = getEndTag(selection, node);
return startTag != null && endTag != null ? startTag.concat(endTag) : null;
};

function typeMatcher(): NodeMatcher {
const delimiterSelector = selectWithLeadingDelimiter(":");
return function (selection: SelectionWithEditor, node: SyntaxNode) {
Expand Down Expand Up @@ -214,13 +176,6 @@ const nodeMatchers: Partial<
),
ifStatement: "if_statement",
anonymousFunction: ["arrow_function", "function"],
name: [
"*[name]",
"optional_parameter.identifier!",
"required_parameter.identifier!",
"augmented_assignment_expression[left]",
"assignment_expression[left]",
],
comment: "comment",
regularExpression: "regex",
className: ["class_declaration[name]", "class[name]"],
Expand Down Expand Up @@ -337,16 +292,6 @@ const nodeMatchers: Partial<
argumentOrParameter: argumentMatcher("formal_parameters", "arguments"),
// XML, JSX
attribute: ["jsx_attribute"],
xmlElement: cascadingMatcher(
matcher(
typedNodeFinder("jsx_element", "jsx_self_closing_element"),
xmlElementExtractor,
),
matcher(typedNodeFinder("jsx_fragment"), jsxFragmentExtractor),
),
xmlBothTags: getTags,
xmlStartTag: getStartTag,
xmlEndTag: getEndTag,
};

export const patternMatchers = createPatternMatchers(nodeMatchers);
Expand Down
19 changes: 0 additions & 19 deletions packages/cursorless-engine/src/util/nodeSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,25 +427,6 @@ export function xmlElementExtractor(
return selection;
}

export function jsxFragmentExtractor(
editor: TextEditor,
node: SyntaxNode,
): SelectionWithContext {
const selection = simpleSelectionExtractor(editor, node);

// Interior range for an element is found by excluding the start and end nodes.
const startPosition = node.children[1].endPosition;
const endPosition = node.children.at(-3)!.startPosition;
selection.context.interiorRange = new Range(
startPosition.row,
startPosition.column,
endPosition.row,
endPosition.column,
);

return selection;
}

export function getInsertionDelimiter(
editor: TextEditor,
leadingDelimiterRange: Range | undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every element
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlElement}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<aaa>
<bbb>ccc</bbb>
<bbb>ddd</bbb>
</aaa>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<aaa>
</aaa>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every element
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlElement}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<>
<bbb>ccc</bbb>
<bbb>ddd</bbb>
</>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<>
</>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every element
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlElement}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<>
<>ccc</>
<>ddd</>
</>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<>
</>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every element
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlElement}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<aaa>
<>ccc</>
<>ddd</>
</aaa>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<aaa>
</aaa>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
- anchor: {line: 2, character: 4}
active: {line: 2, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every end tag
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlEndTag}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<>
<>ccc</>
<>ddd</>
</>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<>
<>ccc
<>ddd
</>
selections:
- anchor: {line: 1, character: 9}
active: {line: 1, character: 9}
- anchor: {line: 2, character: 9}
active: {line: 2, character: 9}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescriptreact
command:
version: 5
spokenForm: clear every end tag
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: xmlEndTag}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
<aaa>
<bbb>ccc</bbb>
<bbb>ddd</bbb>
</aaa>
selections:
- anchor: {line: 1, character: 4}
active: {line: 1, character: 4}
marks: {}
finalState:
documentContents: |-
<aaa>
<bbb>ccc
<bbb>ddd
</aaa>
selections:
- anchor: {line: 1, character: 12}
active: {line: 1, character: 12}
- anchor: {line: 2, character: 12}
active: {line: 2, character: 12}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
languageId: typescript
command:
version: 5
spokenForm: clear every name
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: name}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
const aaa = "bbb";
const ccc = "ddd";
selections:
- anchor: {line: 1, character: 18}
active: {line: 1, character: 18}
marks: {}
finalState:
documentContents: |-
const = "bbb";
const = "ddd";
selections:
- anchor: {line: 0, character: 6}
active: {line: 0, character: 6}
- anchor: {line: 1, character: 6}
active: {line: 1, character: 6}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
languageId: typescript
command:
version: 5
spokenForm: clear every name
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: everyScope
scopeType: {type: name}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
function eee() {
const aaa = "bbb";
const ccc = "ddd";
}
selections:
- anchor: {line: 2, character: 22}
active: {line: 2, character: 22}
marks: {}
finalState:
documentContents: |-
function eee() {
const = "bbb";
const = "ddd";
}
selections:
- anchor: {line: 1, character: 10}
active: {line: 1, character: 10}
- anchor: {line: 2, character: 10}
active: {line: 2, character: 10}
Loading

0 comments on commit 65fab51

Please sign in to comment.