mirror of
https://github.com/cursorless-dev/cursorless.git
synced 2024-10-05 13:27:10 +03:00
Added support for child indexes in the pattern finder (#395)
* Added support for child indexes in the pattern finder * Updated pattern finder to correctly do descending search with optional parents * Clear up any type and not type in pattern finder * Updated go to use new index feature in pattern finder
This commit is contained in:
parent
6f472846fe
commit
d0d41d9a99
@ -1,20 +1,11 @@
|
||||
import {
|
||||
createPatternMatchers,
|
||||
argumentMatcher,
|
||||
leadingMatcher,
|
||||
conditionMatcher,
|
||||
trailingMatcher,
|
||||
childAtIndexMatcher,
|
||||
cascadingMatcher,
|
||||
patternMatcher,
|
||||
} from "../util/nodeMatchers";
|
||||
import {
|
||||
NodeMatcherAlternative,
|
||||
ScopeType,
|
||||
SelectionWithEditor,
|
||||
} from "../typings/Types";
|
||||
import { getNodeRange } from "../util/nodeSelectors";
|
||||
import { SyntaxNode } from "web-tree-sitter";
|
||||
import { NodeMatcherAlternative, ScopeType } from "../typings/Types";
|
||||
|
||||
// Generated by the following command:
|
||||
// `curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-go/master/src/node-types.json | jq '[.[] | select(.type == "_statement" or .type == "_simple_statement") | .subtypes[].type]'`
|
||||
@ -42,7 +33,7 @@ const STATEMENT_TYPES = [
|
||||
"select_statement",
|
||||
"type_declaration",
|
||||
"type_switch_statement",
|
||||
"var_declaration"
|
||||
"var_declaration",
|
||||
];
|
||||
|
||||
const nodeMatchers: Partial<Record<ScopeType, NodeMatcherAlternative>> = {
|
||||
@ -70,11 +61,11 @@ const nodeMatchers: Partial<Record<ScopeType, NodeMatcherAlternative>> = {
|
||||
patternMatcher("argument_declaration")
|
||||
),
|
||||
collectionItem: ["keyed_element", "element"],
|
||||
collectionKey: childAtIndexMatcher(["keyed_element"], 0),
|
||||
collectionKey: "keyed_element[0]",
|
||||
value: cascadingMatcher(
|
||||
childAtIndexMatcher(["keyed_element"], 1),
|
||||
patternMatcher("keyed_element[1]"),
|
||||
patternMatcher("return_statement.expression_list!")
|
||||
)
|
||||
),
|
||||
};
|
||||
|
||||
export default createPatternMatchers(nodeMatchers);
|
||||
export default createPatternMatchers(nodeMatchers);
|
||||
|
@ -0,0 +1,31 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 1
|
||||
spokenForm: take funk cap
|
||||
action: setSelection
|
||||
targets:
|
||||
- type: primitive
|
||||
modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: c}
|
||||
initialState:
|
||||
documentContents: |
|
||||
const myFunk = () => {
|
||||
};
|
||||
selections:
|
||||
- anchor: {line: 2, character: 0}
|
||||
active: {line: 2, character: 0}
|
||||
marks:
|
||||
default.c:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 5}
|
||||
finalState:
|
||||
documentContents: |
|
||||
const myFunk = () => {
|
||||
};
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 2}
|
||||
thatMark:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 2}
|
||||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: c}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}, isImplicit: false}]
|
@ -0,0 +1,31 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 1
|
||||
spokenForm: take funk made
|
||||
action: setSelection
|
||||
targets:
|
||||
- type: primitive
|
||||
modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: m}
|
||||
initialState:
|
||||
documentContents: |
|
||||
const myFunk = () => {
|
||||
};
|
||||
selections:
|
||||
- anchor: {line: 2, character: 0}
|
||||
active: {line: 2, character: 0}
|
||||
marks:
|
||||
default.m:
|
||||
start: {line: 0, character: 6}
|
||||
end: {line: 0, character: 12}
|
||||
finalState:
|
||||
documentContents: |
|
||||
const myFunk = () => {
|
||||
};
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 2}
|
||||
thatMark:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 2}
|
||||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: m}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}, isImplicit: false}]
|
@ -0,0 +1,31 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 1
|
||||
spokenForm: take funk made
|
||||
action: setSelection
|
||||
targets:
|
||||
- type: primitive
|
||||
modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: m}
|
||||
initialState:
|
||||
documentContents: |
|
||||
const myFunk = function() {
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 2, character: 0}
|
||||
active: {line: 2, character: 0}
|
||||
marks:
|
||||
default.m:
|
||||
start: {line: 0, character: 6}
|
||||
end: {line: 0, character: 12}
|
||||
finalState:
|
||||
documentContents: |
|
||||
const myFunk = function() {
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 1}
|
||||
thatMark:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 1}
|
||||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: m}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}, isImplicit: false}]
|
@ -0,0 +1,31 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 1
|
||||
spokenForm: take funk soon
|
||||
action: setSelection
|
||||
targets:
|
||||
- type: primitive
|
||||
modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: s}
|
||||
initialState:
|
||||
documentContents: |
|
||||
const myFunk = function() {
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 2, character: 0}
|
||||
active: {line: 2, character: 0}
|
||||
marks:
|
||||
default.s:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 5}
|
||||
finalState:
|
||||
documentContents: |
|
||||
const myFunk = function() {
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 1}
|
||||
thatMark:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 1, character: 1}
|
||||
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: s}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}, isImplicit: false}]
|
@ -147,32 +147,19 @@ function tryPatternMatch(
|
||||
node: SyntaxNode,
|
||||
patterns: Pattern[]
|
||||
): SyntaxNode | null {
|
||||
const firstPattern = patterns[0];
|
||||
const lastPattern = patterns[patterns.length - 1];
|
||||
let result = searchNodeAscending(node, patterns);
|
||||
|
||||
if (!result && patterns.length > 1) {
|
||||
result = searchNodeDescending(node, patterns);
|
||||
}
|
||||
|
||||
let resultNode: SyntaxNode | null = null;
|
||||
let resultPattern;
|
||||
// Only one type try to match current node.
|
||||
if (patterns.length === 1) {
|
||||
if (firstPattern.typeEquals(node)) {
|
||||
resultNode = node;
|
||||
resultPattern = firstPattern;
|
||||
}
|
||||
} else {
|
||||
// Matched last. Ascending search.
|
||||
if (lastPattern.typeEquals(node)) {
|
||||
const result = searchNodeAscending(node, lastPattern, patterns);
|
||||
if (result != null) {
|
||||
[resultNode, resultPattern] = result;
|
||||
}
|
||||
}
|
||||
// Matched first. Descending search.
|
||||
if (resultNode == null && firstPattern.typeEquals(node)) {
|
||||
const result = searchNodeDescending(node, firstPattern, patterns);
|
||||
if (result != null) {
|
||||
[resultNode, resultPattern] = result;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
[resultNode, resultPattern] = result;
|
||||
}
|
||||
|
||||
// Use field name child if field name is given
|
||||
if (
|
||||
resultNode != null &&
|
||||
@ -180,9 +167,13 @@ function tryPatternMatch(
|
||||
resultPattern.fields != null
|
||||
) {
|
||||
resultPattern.fields.forEach((field) => {
|
||||
resultNode = resultNode?.childForFieldName(field) ?? null;
|
||||
resultNode =
|
||||
(field.isIndex
|
||||
? resultNode?.namedChild(field.value)
|
||||
: resultNode?.childForFieldName(field.value)) ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
return resultNode;
|
||||
}
|
||||
|
||||
@ -190,76 +181,118 @@ type NodePattern = [SyntaxNode, Pattern] | null;
|
||||
|
||||
function searchNodeAscending(
|
||||
node: SyntaxNode,
|
||||
lastPattern: Pattern,
|
||||
patterns: Pattern[]
|
||||
): NodePattern {
|
||||
let resultNode = node;
|
||||
let resultPattern = lastPattern;
|
||||
let important: NodePattern = lastPattern.isImportant
|
||||
? [node, lastPattern]
|
||||
: null;
|
||||
for (let i = patterns.length - 2; i > -1; --i) {
|
||||
let result: NodePattern = null;
|
||||
let currentNode: SyntaxNode | null = node;
|
||||
|
||||
for (let i = patterns.length - 1; i > -1; --i) {
|
||||
const pattern = patterns[i];
|
||||
if (resultNode.parent == null || !pattern.typeEquals(resultNode.parent)) {
|
||||
|
||||
if (currentNode == null || !pattern.typeEquals(currentNode)) {
|
||||
if (pattern.isOptional) {
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
resultNode = resultNode.parent;
|
||||
resultPattern = pattern;
|
||||
if (pattern.isImportant) {
|
||||
important = [resultNode, pattern];
|
||||
|
||||
// Return top node if not important found
|
||||
if (!result || !result[1].isImportant) {
|
||||
result = [currentNode, pattern];
|
||||
}
|
||||
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
return important != null ? important : [resultNode, resultPattern];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function searchNodeDescending(
|
||||
node: SyntaxNode,
|
||||
firstPattern: Pattern,
|
||||
patterns: Pattern[]
|
||||
): NodePattern {
|
||||
let tmpNode = node;
|
||||
// Even if descending search we return the "top" node by default.
|
||||
let important: NodePattern = [node, firstPattern];
|
||||
for (let i = 1; i < patterns.length; ++i) {
|
||||
let result: NodePattern = null;
|
||||
let currentNode: SyntaxNode | null = node;
|
||||
|
||||
for (let i = 0; i < patterns.length; ++i) {
|
||||
const pattern = patterns[i];
|
||||
const children = tmpNode.namedChildren.filter((node) =>
|
||||
pattern.typeEquals(node)
|
||||
);
|
||||
if (children.length !== 1) {
|
||||
|
||||
if (currentNode == null || !pattern.typeEquals(currentNode)) {
|
||||
if (pattern.isOptional) {
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
tmpNode = children[0];
|
||||
if (pattern.isImportant) {
|
||||
important = [tmpNode, pattern];
|
||||
|
||||
// Return top node if not important found
|
||||
if (!result || pattern.isImportant) {
|
||||
result = [currentNode, pattern];
|
||||
}
|
||||
|
||||
if (i + 1 < patterns.length) {
|
||||
const children: SyntaxNode[] = currentNode.namedChildren.filter((node) =>
|
||||
patterns[i + 1].typeEquals(node)
|
||||
);
|
||||
currentNode = children.length === 1 ? children[0] : null;
|
||||
}
|
||||
}
|
||||
return important;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface PatternFieldIndex {
|
||||
isIndex: true;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface PatternFieldName {
|
||||
isIndex: false;
|
||||
value: string;
|
||||
}
|
||||
|
||||
type PatternField = PatternFieldName | PatternFieldIndex;
|
||||
|
||||
class Pattern {
|
||||
type: string;
|
||||
fields: string[] | null = null;
|
||||
isImportant: boolean = false;
|
||||
isOptional: boolean = false;
|
||||
fields: PatternField[];
|
||||
isImportant: boolean;
|
||||
isOptional: boolean;
|
||||
anyType: boolean = false;
|
||||
notType: boolean = false;
|
||||
|
||||
constructor(pattern: string) {
|
||||
this.type = pattern.match(/^[\w*~]+/)![0];
|
||||
this.fields = [...pattern.matchAll(/(?<=\[).+?(?=\])/g)].map((m) => m[0]);
|
||||
if (this.type === "*") {
|
||||
this.anyType = true;
|
||||
} else if (this.type.startsWith("~")) {
|
||||
this.type = this.type.slice(1);
|
||||
this.notType = true;
|
||||
}
|
||||
this.isImportant = pattern.indexOf("!") > -1;
|
||||
this.isOptional = pattern.indexOf("?") > -1;
|
||||
this.fields = [...pattern.matchAll(/(?<=\[).+?(?=\])/g)]
|
||||
.map((m) => m[0])
|
||||
.map((field) => {
|
||||
if (/\d+/.test(field)) {
|
||||
return {
|
||||
isIndex: true,
|
||||
value: Number(field),
|
||||
};
|
||||
}
|
||||
return {
|
||||
isIndex: false,
|
||||
value: field,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
typeEquals(node: SyntaxNode) {
|
||||
return (
|
||||
this.type === node.type ||
|
||||
this.type === "*" ||
|
||||
(this.type.startsWith("~") && this.type.slice(1) !== node.type)
|
||||
);
|
||||
if (this.anyType) {
|
||||
return true;
|
||||
}
|
||||
if (this.notType) {
|
||||
return this.type !== node.type;
|
||||
}
|
||||
return this.type === node.type;
|
||||
}
|
||||
}
|
||||
|
@ -86,20 +86,6 @@ export function argumentMatcher(...parentTypes: string[]): NodeMatcher {
|
||||
export function conditionMatcher(...patterns: string[]): NodeMatcher {
|
||||
return matcher(patternFinder(...patterns), conditionSelectionExtractor);
|
||||
}
|
||||
/**
|
||||
* Given `patterns`, creates a node matcher that selects the named child
|
||||
* at the specified index of the pattern matched node.
|
||||
*
|
||||
* @param patterns Patterns for pattern finder
|
||||
* @param childIdx Index of child
|
||||
* @returns A node matcher
|
||||
*/
|
||||
export function childAtIndexMatcher(patterns: string[], childIdx: number): NodeMatcher {
|
||||
const finder = patternFinder(...patterns);
|
||||
return matcher(
|
||||
(node: SyntaxNode) => finder(node)?.namedChild(childIdx) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given `patterns`, creates a node matcher that will add leading delimiter to
|
||||
|
Loading…
Reference in New Issue
Block a user