From 9ecd3f8a769fa9dc199a18d0eea7aefcbcbff8e7 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 9 Dec 2021 16:07:35 +0000 Subject: [PATCH 1/9] Preliminary clojure support --- src/languages/clojure.ts | 23 +++++++++++++ src/languages/constants.ts | 1 + src/languages/getNodeMatcher.ts | 6 ++-- src/languages/getTextFragmentExtractor.ts | 34 +++++++++++++++++-- src/languages/java.ts | 26 +------------- .../recorded/languages/clojure/clearCall.yml | 23 +++++++++++++ .../recorded/languages/clojure/clearCall2.yml | 23 +++++++++++++ .../languages/clojure/clearComment.yml | 23 +++++++++++++ .../recorded/languages/clojure/clearList.yml | 23 +++++++++++++ .../recorded/languages/clojure/clearList2.yml | 23 +++++++++++++ .../recorded/languages/clojure/clearMap.yml | 23 +++++++++++++ .../parseTree/clojure/clearBound.yml | 27 +++++++++++++++ .../parseTree/clojure/clearCore.yml | 23 +++++++++++++ .../parseTree/clojure/clearCore2.yml | 23 +++++++++++++ .../parseTree/clojure/clearPair.yml | 23 +++++++++++++ .../parseTree/clojure/clearPair2.yml | 23 +++++++++++++ .../parseTree/clojure/clearPair3.yml | 23 +++++++++++++ .../parseTree/clojure/clearPair4.yml | 23 +++++++++++++ 18 files changed, 364 insertions(+), 29 deletions(-) create mode 100644 src/languages/clojure.ts create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearCall.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearCall2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearComment.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearList.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearList2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearMap.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearBound.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore2.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair2.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair3.yml create mode 100644 src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair4.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts new file mode 100644 index 0000000000..bb490f08d4 --- /dev/null +++ b/src/languages/clojure.ts @@ -0,0 +1,23 @@ +import { + createPatternMatchers, + argumentMatcher, + leadingMatcher, + trailingMatcher, +} from "../util/nodeMatchers"; +import { + ScopeType, + NodeMatcherAlternative, + SelectionWithEditor, +} from "../typings/Types"; +import { SyntaxNode } from "web-tree-sitter"; +import { getNodeRange } from "../util/nodeSelectors"; + +const nodeMatchers: Partial> = { + comment: "comment", + map: "map_lit", + list: ["vec_lit", "quoting_lit.list_lit"], + string: "str_lit", + functionCall: "~quoting_lit.list_lit!", +}; + +export default createPatternMatchers(nodeMatchers); diff --git a/src/languages/constants.ts b/src/languages/constants.ts index 2ead5eb26e..86bc029c1e 100644 --- a/src/languages/constants.ts +++ b/src/languages/constants.ts @@ -1,5 +1,6 @@ export const supportedLanguageIds = [ "c", + "clojure", "cpp", "csharp", "java", diff --git a/src/languages/getNodeMatcher.ts b/src/languages/getNodeMatcher.ts index 85ddb7ab84..830790c2b5 100644 --- a/src/languages/getNodeMatcher.ts +++ b/src/languages/getNodeMatcher.ts @@ -8,10 +8,11 @@ import { SelectionWithEditor, } from "../typings/Types"; import cpp from "./cpp"; +import clojure from "./clojure"; import csharp from "./csharp"; import { patternMatchers as json } from "./json"; import { patternMatchers as typescript } from "./typescript"; -import { patternMatchers as java } from "./java"; +import java from "./java"; import python from "./python"; import { UnsupportedLanguageError } from "../errors"; import { SupportedLanguageId } from "./constants"; @@ -45,7 +46,8 @@ const languageMatchers: Record< Record > = { c: cpp, - cpp: cpp, + clojure, + cpp, csharp: csharp, java, javascript: typescript, diff --git a/src/languages/getTextFragmentExtractor.ts b/src/languages/getTextFragmentExtractor.ts index 2ca609667a..1c955c04d7 100644 --- a/src/languages/getTextFragmentExtractor.ts +++ b/src/languages/getTextFragmentExtractor.ts @@ -1,7 +1,6 @@ import { SyntaxNode } from "web-tree-sitter"; import { SelectionWithEditor } from "../typings/Types"; import { stringTextFragmentExtractor as jsonStringTextFragmentExtractor } from "./json"; -import { stringTextFragmentExtractor as javaStringTextFragmentExtractor } from "./java"; import { stringTextFragmentExtractor as typescriptStringTextFragmentExtractor } from "./typescript"; import { UnsupportedLanguageError } from "../errors"; import { Range } from "vscode"; @@ -67,6 +66,33 @@ function constructDefaultStringTextFragmentExtractor( }; } +/** + * Extracts string text fragments in languages that don't have quotation mark + * tokens as children of string tokens, but instead include them in the text of + * the string. + * + * This is a hack. Rather than letting the parse tree handle the quotation marks + * in java, we instead just let the textual surround handle them by letting it + * see the quotation marks. In other languages we prefer to let the parser + * handle the quotation marks in case they are more than one character long. + * @param node The node which might be a string node + * @param selection The selection from which to expand + * @returns The range of the string text or null if the node is not a string + */ +function constructHackedStringTextFragmentExtractor( + languageId: SupportedLanguageId +) { + const stringNodeMatcher = getNodeMatcher(languageId, "string", false); + + return (node: SyntaxNode, selection: SelectionWithEditor) => { + if (stringNodeMatcher(selection, node) != null) { + return getNodeRange(node); + } + + return null; + }; +} + /** * Returns a function which can be used to extract the range of a text fragment * from within a parsed language. This function should only return a nominal @@ -94,11 +120,15 @@ const textFragmentExtractors: Record< TextFragmentExtractor > = { c: constructDefaultTextFragmentExtractor("c"), + clojure: constructDefaultTextFragmentExtractor( + "clojure", + constructHackedStringTextFragmentExtractor("clojure") + ), cpp: constructDefaultTextFragmentExtractor("cpp"), csharp: constructDefaultTextFragmentExtractor("csharp"), java: constructDefaultTextFragmentExtractor( "java", - javaStringTextFragmentExtractor + constructHackedStringTextFragmentExtractor("java") ), javascript: constructDefaultTextFragmentExtractor( "javascript", diff --git a/src/languages/java.ts b/src/languages/java.ts index 20c93c678c..382a031942 100644 --- a/src/languages/java.ts +++ b/src/languages/java.ts @@ -76,28 +76,4 @@ const nodeMatchers: Partial> = { argumentOrParameter: argumentMatcher("formal_parameters", "argument_list"), }; -export const patternMatchers = createPatternMatchers(nodeMatchers); - -/** - * Extracts string text fragments in java. - * - * This is a hack to deal with the fact that java doesn't have - * quotation mark tokens as children of the string. Rather than letting - * the parse tree handle the quotation marks in java, we instead just - * let the textual surround handle them by letting it see the quotation - * marks. In other languages we prefer to let the parser handle the - * quotation marks in case they are more than one character long. - * @param node The node which might be a string node - * @param selection The selection from which to expand - * @returns The range of the string text or null if the node is not a string - */ -export function stringTextFragmentExtractor( - node: SyntaxNode, - selection: SelectionWithEditor -) { - if (node.type === "string_literal") { - return getNodeRange(node); - } - - return null; -} +export default createPatternMatchers(nodeMatchers); diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearCall.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearCall.yml new file mode 100644 index 0000000000..5e5717b25c --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearCall.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear call + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false} +initialState: + documentContents: (hello) + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearCall2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearCall2.yml new file mode 100644 index 0000000000..30bafe20ec --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearCall2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear call + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false} +initialState: + documentContents: (foo '(bar)) + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: functionCall, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearComment.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearComment.yml new file mode 100644 index 0000000000..d56eaa5b67 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearComment.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear comment + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: comment, includeSiblings: false} +initialState: + documentContents: ;; hello + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: comment, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearList.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearList.yml new file mode 100644 index 0000000000..9871b5b06f --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearList.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "'(hello)" + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearList2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearList2.yml new file mode 100644 index 0000000000..2d534b67d2 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearList2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear list + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: list, includeSiblings: false} +initialState: + documentContents: "[hello]" + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: list, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearMap.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearMap.yml new file mode 100644 index 0000000000..fbd9601141 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearMap.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear map + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: map, includeSiblings: false} +initialState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: map, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearBound.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearBound.yml new file mode 100644 index 0000000000..fa14465411 --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearBound.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear bound + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: excludeInterior} +initialState: + documentContents: (hello) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: hello + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: excludeInterior}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore.yml new file mode 100644 index 0000000000..64a6d7a07d --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear core + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly} +initialState: + documentContents: (hello) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: () + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore2.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore2.yml new file mode 100644 index 0000000000..182e2415d5 --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearCore2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear core + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly} +initialState: + documentContents: "\"hello\"" + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: "\"\"" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any, delimiterInclusion: interiorOnly}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair.yml new file mode 100644 index 0000000000..fee3c533d3 --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear pair + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any} +initialState: + documentContents: ;; (hello) + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + marks: {} +finalState: + documentContents: ";; " + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + thatMark: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair2.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair2.yml new file mode 100644 index 0000000000..2d507b9f18 --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear pair + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any} +initialState: + documentContents: (hello) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair3.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair3.yml new file mode 100644 index 0000000000..4d17cdb09d --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair3.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear pair + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any} +initialState: + documentContents: "\"hello\"" + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}] diff --git a/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair4.yml b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair4.yml new file mode 100644 index 0000000000..0268532496 --- /dev/null +++ b/src/test/suite/fixtures/recorded/surroundingPair/parseTree/clojure/clearPair4.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear pair + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: surroundingPair, delimiter: any} +initialState: + documentContents: "\"(hello)\"" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "\"\"" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}] From e62f58057f24a13b443868855a01fe8cfb97819c Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 9 Dec 2021 16:11:43 +0000 Subject: [PATCH 2/9] Cleanup --- src/languages/clojure.ts | 20 +++++++------------- src/languages/java.ts | 8 +------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index bb490f08d4..f18acdf2a2 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -1,22 +1,16 @@ -import { - createPatternMatchers, - argumentMatcher, - leadingMatcher, - trailingMatcher, -} from "../util/nodeMatchers"; -import { - ScopeType, - NodeMatcherAlternative, - SelectionWithEditor, -} from "../typings/Types"; -import { SyntaxNode } from "web-tree-sitter"; -import { getNodeRange } from "../util/nodeSelectors"; +import { createPatternMatchers } from "../util/nodeMatchers"; +import { ScopeType, NodeMatcherAlternative } from "../typings/Types"; const nodeMatchers: Partial> = { comment: "comment", map: "map_lit", + + // A list is either a vector literal or a quoted list literal list: ["vec_lit", "quoting_lit.list_lit"], + string: "str_lit", + + // A function call is a list literal which is not quoted functionCall: "~quoting_lit.list_lit!", }; diff --git a/src/languages/java.ts b/src/languages/java.ts index 382a031942..507d2ad2c3 100644 --- a/src/languages/java.ts +++ b/src/languages/java.ts @@ -5,13 +5,7 @@ import { conditionMatcher, trailingMatcher, } 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-java/master/src/node-types.json | jq '[.[] | select(.type == "statement" or .type == "declaration") | .subtypes[].type]' From 6de5fe2180e0e065924d0c1f73c329cfb370e855 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 9 Dec 2021 19:09:47 +0000 Subject: [PATCH 3/9] Support clojure key, value, and item --- src/languages/clojure.ts | 49 ++++++++++++++++++- .../languages/clojure/chuckItemFine.yml | 36 ++++++++++++++ .../languages/clojure/chuckItemFine2.yml | 34 +++++++++++++ .../languages/clojure/chuckItemZip.yml | 37 ++++++++++++++ .../languages/clojure/chuckItemZip2.yml | 34 +++++++++++++ .../languages/clojure/clearItemBat.yml | 35 +++++++++++++ .../clojure/clearItemBatClearItemBat.yml | 27 ++++++++++ .../languages/clojure/clearItemFine.yml | 27 ++++++++++ .../languages/clojure/clearItemWhale.yml | 27 ++++++++++ .../languages/clojure/clearItemZip.yml | 27 ++++++++++ .../recorded/languages/clojure/clearKey.yml | 23 +++++++++ .../languages/clojure/clearKeyWhale.yml | 27 ++++++++++ .../languages/clojure/clearKeyWhale2.yml | 37 ++++++++++++++ .../languages/clojure/clearKeyZip.yml | 27 ++++++++++ .../languages/clojure/clearKeyZip2.yml | 37 ++++++++++++++ .../languages/clojure/clearValueBat.yml | 37 ++++++++++++++ .../languages/clojure/clearValueZip.yml | 37 ++++++++++++++ .../recorded/languages/clojure/takeValue.yml | 23 +++++++++ .../languages/clojure/takeValueBat.yml | 27 ++++++++++ .../languages/clojure/takeValueBat2.yml | 27 ++++++++++ .../languages/clojure/takeValueFine.yml | 27 ++++++++++ .../languages/clojure/takeValueFine2.yml | 27 ++++++++++ .../languages/clojure/takeValueWhale.yml | 27 ++++++++++ .../languages/clojure/takeValueZip.yml | 27 ++++++++++ .../languages/clojure/takeValueZip2.yml | 27 ++++++++++ src/util/nodeSelectors.ts | 26 +++++++--- src/util/treeSitterUtils.ts | 22 +++++++++ 27 files changed, 810 insertions(+), 8 deletions(-) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItemBat.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItemBatClearItemBat.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItemFine.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItemWhale.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItemZip.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearKey.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearValueBat.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearValueZip.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValue.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueBat.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueBat2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueFine.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueFine2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueWhale.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueZip.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/takeValueZip2.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index f18acdf2a2..9def075f15 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -1,10 +1,55 @@ -import { createPatternMatchers } from "../util/nodeMatchers"; -import { ScopeType, NodeMatcherAlternative } from "../typings/Types"; +import { createPatternMatchers, matcher } from "../util/nodeMatchers"; +import { + ScopeType, + NodeMatcherAlternative, + SelectionWithContext, +} from "../typings/Types"; +import { SyntaxNode } from "web-tree-sitter"; +import { Position, Selection, TextEditor } from "vscode"; +import { + delimitedSelector, + makeRangeFromPositions, +} from "../util/nodeSelectors"; +import { identity } from "lodash"; +import { getChildNodesForFieldName } from "../util/treeSitterUtils"; + +function parityNodeFinder(parity: 0 | 1) { + return (node: SyntaxNode) => { + const parent = node.parent; + + if ( + parent == null || + parent.type !== "map_lit" || + node.type === "{" || + node.type === "}" + ) { + return null; + } + + const valueNodes = getChildNodesForFieldName(parent, "value"); + + const nodeIndex = valueNodes.findIndex(({ id }) => id === node.id); + + return valueNodes[Math.floor(nodeIndex / 2) * 2 + parity]; + }; +} const nodeMatchers: Partial> = { comment: "comment", map: "map_lit", + collectionKey: matcher(parityNodeFinder(0)), + collectionItem: matcher( + parityNodeFinder(0), + delimitedSelector( + (node) => node.type === "{" || node.type === "}", + ", ", + identity, + parityNodeFinder(1) as (node: SyntaxNode) => SyntaxNode + ) + ), + value: matcher(parityNodeFinder(1)), + // A list is either a vector literal or a quoted list literal list: ["vec_lit", "quoting_lit.list_lit"], diff --git a/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine.yml b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine.yml new file mode 100644 index 0000000000..f681967189 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine.yml @@ -0,0 +1,36 @@ +languageId: clojure +command: + version: 0 + spokenForm: chuck item fine + action: remove + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.f: + start: {line: 1, character: 5} + end: {line: 1, character: 8} +finalState: + documentContents: |- + { + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 3, character: 1} + active: {line: 3, character: 1} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine2.yml b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine2.yml new file mode 100644 index 0000000000..1a0800288e --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemFine2.yml @@ -0,0 +1,34 @@ +languageId: clojure +command: + version: 0 + spokenForm: chuck item fine + action: remove + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + :baz "whatever", + } + selections: + - anchor: {line: 3, character: 1} + active: {line: 3, character: 1} + marks: + default.f: + start: {line: 1, character: 5} + end: {line: 1, character: 8} +finalState: + documentContents: |- + { + :baz "whatever", + } + selections: + - anchor: {line: 2, character: 1} + active: {line: 2, character: 1} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip.yml b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip.yml new file mode 100644 index 0000000000..56537e57ba --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip.yml @@ -0,0 +1,37 @@ +languageId: clojure +command: + version: 0 + spokenForm: chuck item zip + action: remove + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.z: + start: {line: 3, character: 5} + end: {line: 3, character: 8} +finalState: + documentContents: |- + { + :foo "bar", + ;; hello + , + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + thatMark: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip2.yml b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip2.yml new file mode 100644 index 0000000000..669fe853e1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/chuckItemZip2.yml @@ -0,0 +1,34 @@ +languageId: clojure +command: + version: 0 + spokenForm: chuck item zip + action: remove + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + :baz "whatever", + } + selections: + - anchor: {line: 3, character: 1} + active: {line: 3, character: 1} + marks: + default.z: + start: {line: 2, character: 5} + end: {line: 2, character: 8} +finalState: + documentContents: |- + { + :foo "bar", + } + selections: + - anchor: {line: 2, character: 1} + active: {line: 2, character: 1} + thatMark: + - anchor: {line: 1, character: 14} + active: {line: 1, character: 14} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItemBat.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItemBat.yml new file mode 100644 index 0000000000..681632ab9e --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItemBat.yml @@ -0,0 +1,35 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item bat + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :baz + ;; hello + "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.b: + start: {line: 1, character: 5} + end: {line: 1, character: 8} +finalState: + documentContents: |- + { + , + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItemBatClearItemBat.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItemBatClearItemBat.yml new file mode 100644 index 0000000000..4d69bb45a8 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItemBatClearItemBat.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item bat clear item bat + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + marks: + default.b: + start: {line: 0, character: 7} + end: {line: 0, character: 10} +finalState: + documentContents: "{}" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItemFine.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItemFine.yml new file mode 100644 index 0000000000..8c92e6f215 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItemFine.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item fine + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\" :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 28} + active: {line: 0, character: 28} + marks: + default.f: + start: {line: 0, character: 2} + end: {line: 0, character: 5} +finalState: + documentContents: "{ :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItemWhale.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItemWhale.yml new file mode 100644 index 0000000000..6518d1b4c6 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItemWhale.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item whale + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.w: + start: {line: 0, character: 19} + end: {line: 0, character: 27} +finalState: + documentContents: "{:foo \"bar\", }" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + thatMark: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItemZip.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItemZip.yml new file mode 100644 index 0000000000..70353e8d4a --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItemZip.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item zip + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.z: + start: {line: 0, character: 14} + end: {line: 0, character: 17} +finalState: + documentContents: "{:foo \"bar\", }" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + thatMark: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearKey.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearKey.yml new file mode 100644 index 0000000000..79fc5686ca --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearKey.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear key + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} +initialState: + documentContents: "{:baz {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "{ {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale.yml new file mode 100644 index 0000000000..2152b29266 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear key whale + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.w: + start: {line: 0, character: 19} + end: {line: 0, character: 27} +finalState: + documentContents: "{:foo \"bar\", \"whatever\"}" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + thatMark: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale2.yml new file mode 100644 index 0000000000..1af0ab879c --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyWhale2.yml @@ -0,0 +1,37 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear key whale + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :baz + ;; hello + "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.w: + start: {line: 3, character: 5} + end: {line: 3, character: 13} +finalState: + documentContents: |- + { + + ;; hello + "whatever", + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip.yml new file mode 100644 index 0000000000..6ad296d569 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear key zip + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.z: + start: {line: 0, character: 14} + end: {line: 0, character: 17} +finalState: + documentContents: "{:foo \"bar\", \"whatever\"}" + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + thatMark: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip2.yml new file mode 100644 index 0000000000..4d348209ce --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearKeyZip2.yml @@ -0,0 +1,37 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear key zip + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.z: + start: {line: 3, character: 5} + end: {line: 3, character: 8} +finalState: + documentContents: |- + { + :foo "bar", + ;; hello + "whatever", + } + selections: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearValueBat.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearValueBat.yml new file mode 100644 index 0000000000..cb579acc6a --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearValueBat.yml @@ -0,0 +1,37 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear value bat + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :baz + ;; hello + "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.b: + start: {line: 1, character: 5} + end: {line: 1, character: 8} +finalState: + documentContents: |- + { + :baz + ;; hello + , + } + selections: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearValueZip.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearValueZip.yml new file mode 100644 index 0000000000..58aaf055f3 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearValueZip.yml @@ -0,0 +1,37 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear value zip + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: |- + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 1} + active: {line: 4, character: 1} + marks: + default.z: + start: {line: 3, character: 5} + end: {line: 3, character: 8} +finalState: + documentContents: |- + { + :foo "bar", + ;; hello + :baz , + } + selections: + - anchor: {line: 3, character: 9} + active: {line: 3, character: 9} + thatMark: + - anchor: {line: 3, character: 9} + active: {line: 3, character: 9} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValue.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValue.yml new file mode 100644 index 0000000000..ddb7fb6d41 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValue.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} +initialState: + documentContents: "{:bar {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "{:bar {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 18} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 18} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat.yml new file mode 100644 index 0000000000..9f9ead1f01 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value bat + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.b: + start: {line: 0, character: 7} + end: {line: 0, character: 10} +finalState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat2.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat2.yml new file mode 100644 index 0000000000..74bcf78854 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueBat2.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value bat + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + marks: + default.b: + start: {line: 0, character: 7} + end: {line: 0, character: 10} +finalState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine.yml new file mode 100644 index 0000000000..eda182cdf0 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value fine + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.f: + start: {line: 0, character: 2} + end: {line: 0, character: 5} +finalState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine2.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine2.yml new file mode 100644 index 0000000000..46e34546c1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueFine2.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value fine + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + marks: + default.f: + start: {line: 0, character: 2} + end: {line: 0, character: 5} +finalState: + documentContents: "{:foo \"bar\"}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 11} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: f, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueWhale.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueWhale.yml new file mode 100644 index 0000000000..5fcfd485e1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueWhale.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value whale + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.w: + start: {line: 0, character: 19} + end: {line: 0, character: 27} +finalState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 28} + thatMark: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 28} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: w, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip.yml new file mode 100644 index 0000000000..823f42f27a --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value zip + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 29} + active: {line: 0, character: 29} + marks: + default.z: + start: {line: 0, character: 14} + end: {line: 0, character: 17} +finalState: + documentContents: "{:foo \"bar\", :baz \"whatever\"}" + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 28} + thatMark: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 28} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip2.yml b/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip2.yml new file mode 100644 index 0000000000..c7153a500c --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/takeValueZip2.yml @@ -0,0 +1,27 @@ +languageId: clojure +command: + version: 0 + spokenForm: take value zip + action: setSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: false} + mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true} +initialState: + documentContents: "{:baz {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: + default.z: + start: {line: 0, character: 2} + end: {line: 0, character: 5} +finalState: + documentContents: "{:baz {:foo \"bar\"}}" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 18} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 18} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: z, usePrePhraseSnapshot: true}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/util/nodeSelectors.ts b/src/util/nodeSelectors.ts index 9293c6782b..a12c74e11a 100644 --- a/src/util/nodeSelectors.ts +++ b/src/util/nodeSelectors.ts @@ -1,6 +1,7 @@ import { SyntaxNode, Point } from "web-tree-sitter"; import { Position, Range, Selection, TextEditor } from "vscode"; import { SelectionWithContext, SelectionExtractor } from "../typings/Types"; +import { identity } from "lodash"; export function makeRangeFromPositions( startPosition: Point, @@ -202,22 +203,29 @@ export function delimitersSelector(...delimiters: string[]) { export function delimitedSelector( isDelimiterNode: (node: SyntaxNode) => boolean, - defaultDelimiter: string + defaultDelimiter: string, + getStartNode: (node: SyntaxNode) => SyntaxNode = identity, + getEndNode: (node: SyntaxNode) => SyntaxNode = identity ): SelectionExtractor { return (editor: TextEditor, node: SyntaxNode) => { let containingListDelimiter: string | null = null; let leadingDelimiterRange: Range | null = null; let trailingDelimiterRange: Range | null = null; + const startNode = getStartNode(node); + const endNode = getEndNode(node); - const nextNonDelimiterNode = getNextNonDelimiterNode(node, isDelimiterNode); + const nextNonDelimiterNode = getNextNonDelimiterNode( + endNode, + isDelimiterNode + ); const previousNonDelimiterNode = getPreviousNonDelimiterNode( - node, + startNode, isDelimiterNode ); if (nextNonDelimiterNode != null) { trailingDelimiterRange = makeRangeFromPositions( - node.endPosition, + endNode.endPosition, nextNonDelimiterNode.startPosition ); @@ -227,7 +235,7 @@ export function delimitedSelector( if (previousNonDelimiterNode != null) { leadingDelimiterRange = makeRangeFromPositions( previousNonDelimiterNode.endPosition, - node.startPosition + startNode.startPosition ); if (containingListDelimiter == null) { @@ -242,7 +250,13 @@ export function delimitedSelector( } return { - ...simpleSelectionExtractor(editor, node), + selection: new Selection( + new Position( + startNode.startPosition.row, + startNode.startPosition.column + ), + new Position(endNode.endPosition.row, endNode.endPosition.column) + ), context: { isInDelimitedList: true, containingListDelimiter, diff --git a/src/util/treeSitterUtils.ts b/src/util/treeSitterUtils.ts index 4509d31e22..478823814a 100644 --- a/src/util/treeSitterUtils.ts +++ b/src/util/treeSitterUtils.ts @@ -14,3 +14,25 @@ export const getDefinitionNode = (node: SyntaxNode) => export const getDeclarationNode = (node: SyntaxNode) => node.childForFieldName("declarator"); + +export function getChildNodesForFieldName( + node: SyntaxNode, + fieldName: string +): SyntaxNode[] { + const treeCursor = node.walk(); + treeCursor.gotoFirstChild(); + + const ret = []; + + let hasNext = true; + + while (hasNext) { + if (treeCursor.currentFieldName() === fieldName) { + ret.push(treeCursor.currentNode()); + } + + hasNext = treeCursor.gotoNextSibling(); + } + + return ret; +} From 31bdfbe2094f6634affa0fe910be217c2ed50c37 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 9 Dec 2021 19:17:18 +0000 Subject: [PATCH 4/9] Add tests for multiple cursors --- .../languages/clojure/clearEveryItem.yml | 39 +++++++++++++++++++ .../languages/clojure/clearEveryKey.yml | 39 +++++++++++++++++++ .../languages/clojure/clearEveryValue.yml | 39 +++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearEveryItem.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearEveryKey.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearEveryValue.yml diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearEveryItem.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryItem.yml new file mode 100644 index 0000000000..1b44562896 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryItem.yml @@ -0,0 +1,39 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear every item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: true} +initialState: + documentContents: |- + + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 13} + active: {line: 4, character: 13} + marks: {} +finalState: + documentContents: |- + + { + , + ;; hello + , + } + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} + thatMark: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: true}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearEveryKey.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryKey.yml new file mode 100644 index 0000000000..4b90516c91 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryKey.yml @@ -0,0 +1,39 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear every key + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true} +initialState: + documentContents: |- + + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 13} + active: {line: 4, character: 13} + marks: {} +finalState: + documentContents: |- + + { + "bar", + ;; hello + "whatever", + } + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} + thatMark: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: true}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearEveryValue.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryValue.yml new file mode 100644 index 0000000000..8f7660574c --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearEveryValue.yml @@ -0,0 +1,39 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear every value + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: value, includeSiblings: true} +initialState: + documentContents: |- + + { + :foo "bar", + ;; hello + :baz "whatever", + } + selections: + - anchor: {line: 4, character: 13} + active: {line: 4, character: 13} + marks: {} +finalState: + documentContents: |- + + { + :foo , + ;; hello + :baz , + } + selections: + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} + thatMark: + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 9} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: value, includeSiblings: true}}] From a89f4643f9e1ec4f003c35c292ebfa6fdced76fc Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Fri, 10 Dec 2021 10:02:38 +0000 Subject: [PATCH 5/9] Handle case where selection inside comment --- src/languages/clojure.ts | 6 ++++ .../recorded/languages/clojure/clearItem.yml | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index 9def075f15..f8540dec92 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -30,6 +30,12 @@ function parityNodeFinder(parity: 0 | 1) { const nodeIndex = valueNodes.findIndex(({ id }) => id === node.id); + if (nodeIndex === -1) { + // TODO: In the future we might conceivably try to handle saying "take + // item" when the selection is inside a comment between the key and value + return null; + } + return valueNodes[Math.floor(nodeIndex / 2) * 2 + parity]; }; } diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem.yml new file mode 100644 index 0000000000..2ecf967339 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem.yml @@ -0,0 +1,33 @@ +languageId: clojure +command: + version: 0 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: |- + { + :bongo { + :foo "bar", + ;; hello + :baz "whatever", + } + } + selections: + - anchor: {line: 3, character: 13} + active: {line: 3, character: 13} + marks: {} +finalState: + documentContents: |- + { + + } + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] From fb65e3a7ebe9cd2e543050ba482f0d8dc501c340 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sat, 11 Dec 2021 18:58:45 +0000 Subject: [PATCH 6/9] Add some basic argument support --- src/languages/clojure.ts | 65 +++++++++++++------ .../recorded/languages/clojure/clearArgue.yml | 43 ++++++++++++ .../languages/clojure/clearArgue2.yml | 23 +++++++ .../recorded/languages/clojure/clearItem2.yml | 23 +++++++ 4 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearArgue.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearArgue2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem2.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index f8540dec92..4192e7ae11 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -1,25 +1,31 @@ -import { createPatternMatchers, matcher } from "../util/nodeMatchers"; import { - ScopeType, - NodeMatcherAlternative, - SelectionWithContext, -} from "../typings/Types"; + cascadingMatcher, + createPatternMatchers, + matcher, +} from "../util/nodeMatchers"; +import { ScopeType, NodeMatcherAlternative } from "../typings/Types"; import { SyntaxNode } from "web-tree-sitter"; -import { Position, Selection, TextEditor } from "vscode"; -import { - delimitedSelector, - makeRangeFromPositions, -} from "../util/nodeSelectors"; +import { delimitedSelector } from "../util/nodeSelectors"; import { identity } from "lodash"; import { getChildNodesForFieldName } from "../util/treeSitterUtils"; function parityNodeFinder(parity: 0 | 1) { + return indexNodeFinder( + (nodeIndex: number) => Math.floor(nodeIndex / 2) * 2 + parity, + "map_lit" + ); +} + +function indexNodeFinder( + indexMatcher: (index: number) => number, + parentType?: string +) { return (node: SyntaxNode) => { const parent = node.parent; if ( parent == null || - parent.type !== "map_lit" || + (parentType != null && parent.type !== parentType) || node.type === "{" || node.type === "}" ) { @@ -36,26 +42,45 @@ function parityNodeFinder(parity: 0 | 1) { return null; } - return valueNodes[Math.floor(nodeIndex / 2) * 2 + parity]; + const desiredIndex = indexMatcher(nodeIndex); + + if (desiredIndex === -1) { + return null; + } + + return valueNodes[desiredIndex]; }; } +function itemFinder() { + return indexNodeFinder((nodeIndex: number) => nodeIndex); +} + const nodeMatchers: Partial> = { comment: "comment", map: "map_lit", collectionKey: matcher(parityNodeFinder(0)), - collectionItem: matcher( - parityNodeFinder(0), - delimitedSelector( - (node) => node.type === "{" || node.type === "}", - ", ", - identity, - parityNodeFinder(1) as (node: SyntaxNode) => SyntaxNode - ) + collectionItem: cascadingMatcher( + matcher( + parityNodeFinder(0), + delimitedSelector( + (node) => node.type === "{" || node.type === "}", + ", ", + identity, + parityNodeFinder(1) as (node: SyntaxNode) => SyntaxNode + ) + ), + // TODO: Require parent not to be a function call + matcher(itemFinder()) ), value: matcher(parityNodeFinder(1)), + // TODO: Require parent to actually be a function call + argumentOrParameter: matcher( + indexNodeFinder((nodeIndex: number) => (nodeIndex !== 0 ? nodeIndex : -1)) + ), + // A list is either a vector literal or a quoted list literal list: ["vec_lit", "quoting_lit.list_lit"], diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearArgue.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue.yml new file mode 100644 index 0000000000..e0fef982b3 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue.yml @@ -0,0 +1,43 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear argue + action: clearAndSetSelection + targets: + - type: primitive + modifier: + { + type: containingScope, + scopeType: argumentOrParameter, + includeSiblings: false, + } +initialState: + documentContents: (foo :bar) + selections: + - anchor: { line: 0, character: 6 } + active: { line: 0, character: 6 } + marks: {} +finalState: + documentContents: (foo ) + selections: + - anchor: { line: 0, character: 5 } + active: { line: 0, character: 5 } + thatMark: + - anchor: { line: 0, character: 5 } + active: { line: 0, character: 5 } +fullTargets: + [ + { + type: primitive, + mark: { type: cursor }, + selectionType: token, + position: contents, + insideOutsideType: inside, + modifier: + { + type: containingScope, + scopeType: argumentOrParameter, + includeSiblings: false, + }, + }, + ] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearArgue2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue2.yml new file mode 100644 index 0000000000..0ec0032a5f --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear argue + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: false} +initialState: + documentContents: (hello (foo :bar)) + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + marks: {} +finalState: + documentContents: (hello ) + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + thatMark: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem2.yml new file mode 100644 index 0000000000..e5e37e6457 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: (foo :bar) + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: (foo ) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] From 55936faab1b0402ecfd35b704716c6a9dab93a39 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Tue, 14 Dec 2021 22:16:41 +0000 Subject: [PATCH 7/9] More work on clojure --- src/languages/clojure.ts | 135 ++++++++++++++---- .../languages/clojure/clearArgue3.yml | 23 +++ .../recorded/languages/clojure/clearFunk.yml | 26 ++++ .../recorded/languages/clojure/clearFunk2.yml | 23 +++ .../recorded/languages/clojure/clearItem3.yml | 23 +++ .../recorded/languages/clojure/clearItem4.yml | 23 +++ .../recorded/languages/clojure/clearItem5.yml | 23 +++ .../recorded/languages/clojure/clearItem6.yml | 23 +++ .../languages/clojure/clearLambda.yml | 23 +++ .../languages/clojure/clearLambda2.yml | 23 +++ .../recorded/languages/clojure/clearName.yml | 29 ++++ .../recorded/languages/clojure/clearName2.yml | 23 +++ src/util/nodeSelectors.ts | 13 ++ 13 files changed, 384 insertions(+), 26 deletions(-) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearArgue3.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearFunk.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearFunk2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem3.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem4.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem5.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearItem6.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearLambda.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearLambda2.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearName.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearName2.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index 4192e7ae11..6ac21782dc 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -2,37 +2,60 @@ import { cascadingMatcher, createPatternMatchers, matcher, + patternMatcher, } from "../util/nodeMatchers"; -import { ScopeType, NodeMatcherAlternative } from "../typings/Types"; +import { + ScopeType, + NodeMatcherAlternative, + NodeFinder, +} from "../typings/Types"; import { SyntaxNode } from "web-tree-sitter"; import { delimitedSelector } from "../util/nodeSelectors"; -import { identity } from "lodash"; +import { flow, identity } from "lodash"; import { getChildNodesForFieldName } from "../util/treeSitterUtils"; - -function parityNodeFinder(parity: 0 | 1) { +import { patternFinder } from "../util/nodeFinders"; + +/** + * Picks a node by rounding down and using the given parity. This function is + * useful for picking the picking eg the key in a sequence of key-value pairs + * @param parentFinder The finder to use to determine whether the parent is a + * match + * @param parity The parity that we're looking for + * @returns A node finder + */ +function parityNodeFinder(parentFinder: NodeFinder, parity: 0 | 1) { return indexNodeFinder( - (nodeIndex: number) => Math.floor(nodeIndex / 2) * 2 + parity, - "map_lit" + parentFinder, + (nodeIndex: number) => Math.floor(nodeIndex / 2) * 2 + parity ); } +function mapParityNodeFinder(parity: 0 | 1) { + return parityNodeFinder(patternFinder("map_lit"), parity); +} + +/** + * Creates a node finder which will apply a transformation to the index of a + * value node and return the node at the given index of the nodes parent + * @param parentFinder A finder which will be applied to the parent to determine + * whether it is a match + * @param indexTransform A function that will be applied to the index of the + * value node. The node at the given index will be used instead of the node + * itself + * @returns A node finder based on the given description + */ function indexNodeFinder( - indexMatcher: (index: number) => number, - parentType?: string + parentFinder: NodeFinder, + indexTransform: (index: number) => number ) { return (node: SyntaxNode) => { const parent = node.parent; - if ( - parent == null || - (parentType != null && parent.type !== parentType) || - node.type === "{" || - node.type === "}" - ) { + if (parent == null || parentFinder(parent) == null) { return null; } - const valueNodes = getChildNodesForFieldName(parent, "value"); + const valueNodes = getValueNodes(parent); const nodeIndex = valueNodes.findIndex(({ id }) => id === node.id); @@ -42,7 +65,7 @@ function indexNodeFinder( return null; } - const desiredIndex = indexMatcher(nodeIndex); + const desiredIndex = indexTransform(nodeIndex); if (desiredIndex === -1) { return null; @@ -53,32 +76,81 @@ function indexNodeFinder( } function itemFinder() { - return indexNodeFinder((nodeIndex: number) => nodeIndex); + return indexNodeFinder( + (node) => node, + (nodeIndex: number) => nodeIndex + ); +} + +/** + * Return the "value" node children of a given node. These are the items in a list + * @param node The node whose children to get + * @returns A list of the value node children of the given node + */ +const getValueNodes = (node: SyntaxNode) => + getChildNodesForFieldName(node, "value"); + +// A function call is a list literal which is not quoted +const functionCallPattern = "~quoting_lit.list_lit!"; +const functionCallFinder = patternFinder(functionCallPattern); + +/** + * Matches a function call if the name of the function is one of the given names + * @param names The acceptable function names + * @returns The function call node if the name matches otherwise null + */ +function functionNameBasedFinder(...names: string[]) { + return (node: SyntaxNode) => { + const functionCallNode = functionCallFinder(node); + if (functionCallNode == null) { + return null; + } + + const functionNode = getValueNodes(functionCallNode)[0]; + + return names.includes(functionNode?.text) ? functionCallNode : null; + }; +} + +function functionNameBasedMatcher(...names: string[]) { + return matcher(functionNameBasedFinder(...names)); } +const functionFinder = functionNameBasedFinder("defn", "defmacro"); + +const functionNameMatcher = matcher( + flow(functionFinder, (functionNode) => + functionNode == null ? null : getValueNodes(functionNode)[1] + ) +); + const nodeMatchers: Partial> = { comment: "comment", map: "map_lit", - collectionKey: matcher(parityNodeFinder(0)), + collectionKey: matcher(mapParityNodeFinder(0)), collectionItem: cascadingMatcher( + // Treat each key value pair as a single item if we're in a map matcher( - parityNodeFinder(0), + mapParityNodeFinder(0), delimitedSelector( (node) => node.type === "{" || node.type === "}", ", ", identity, - parityNodeFinder(1) as (node: SyntaxNode) => SyntaxNode + mapParityNodeFinder(1) as (node: SyntaxNode) => SyntaxNode ) ), - // TODO: Require parent not to be a function call + + // Otherwise just treat every item within a list as an item matcher(itemFinder()) ), - value: matcher(parityNodeFinder(1)), + value: matcher(mapParityNodeFinder(1)), - // TODO: Require parent to actually be a function call + // TODO: Handle formal parameters argumentOrParameter: matcher( - indexNodeFinder((nodeIndex: number) => (nodeIndex !== 0 ? nodeIndex : -1)) + indexNodeFinder(patternFinder(functionCallPattern), (nodeIndex: number) => + nodeIndex !== 0 ? nodeIndex : -1 + ) ), // A list is either a vector literal or a quoted list literal @@ -86,8 +158,19 @@ const nodeMatchers: Partial> = { string: "str_lit", - // A function call is a list literal which is not quoted - functionCall: "~quoting_lit.list_lit!", + functionCall: functionCallPattern, + + namedFunction: matcher(functionFinder), + + functionName: functionNameMatcher, + + // TODO: Handle `let` declarations, defs, etc + name: functionNameMatcher, + + anonymousFunction: cascadingMatcher( + functionNameBasedMatcher("fn"), + patternMatcher("anon_fn_lit") + ), }; export default createPatternMatchers(nodeMatchers); diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearArgue3.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue3.yml new file mode 100644 index 0000000000..aaa1788ed1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearArgue3.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear argue + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: false} +initialState: + documentContents: (foo '(hello there)) + selections: + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} + marks: {} +finalState: + documentContents: (foo ) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: argumentOrParameter, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearFunk.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearFunk.yml new file mode 100644 index 0000000000..c84e629bd3 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearFunk.yml @@ -0,0 +1,26 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear funk + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false} +initialState: + documentContents: |- + (defn say-hello + [name] + (println (str "Hello, " name))) + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearFunk2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearFunk2.yml new file mode 100644 index 0000000000..a63eb4e510 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearFunk2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear funk + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false} +initialState: + documentContents: (defmacro foo [bar] "baz" (bongo)) + selections: + - anchor: {line: 0, character: 30} + active: {line: 0, character: 30} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: namedFunction, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem3.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem3.yml new file mode 100644 index 0000000000..0114c2c0d7 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem3.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: (foo) + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: () + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem4.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem4.yml new file mode 100644 index 0000000000..b71ffc8a78 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem4.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: (foo (hello there)) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + marks: {} +finalState: + documentContents: (foo ) + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem5.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem5.yml new file mode 100644 index 0000000000..e0b5b48cad --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem5.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: "'(foo bar)" + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: "'( bar)" + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + thatMark: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearItem6.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearItem6.yml new file mode 100644 index 0000000000..fc7311c5b4 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearItem6.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear item + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false} +initialState: + documentContents: "[foo :bar]" + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + marks: {} +finalState: + documentContents: "[foo ]" + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: collectionItem, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearLambda.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearLambda.yml new file mode 100644 index 0000000000..c45ff257b5 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearLambda.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear lambda + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: anonymousFunction, includeSiblings: false} +initialState: + documentContents: (fn [] (println "Hello world")) + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: anonymousFunction, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearLambda2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearLambda2.yml new file mode 100644 index 0000000000..2d1beed854 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearLambda2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear lambda + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: anonymousFunction, includeSiblings: false} +initialState: + documentContents: "#(+ 1 1)" + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: anonymousFunction, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearName.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearName.yml new file mode 100644 index 0000000000..85b8629c99 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearName.yml @@ -0,0 +1,29 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear name + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: name, includeSiblings: false} +initialState: + documentContents: |- + (defn say-hello + [name] + (println (str "Hello, " name))) + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + (defn + [name] + (println (str "Hello, " name))) + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + thatMark: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: name, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearName2.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearName2.yml new file mode 100644 index 0000000000..e512e0951b --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearName2.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear name + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: name, includeSiblings: false} +initialState: + documentContents: (defmacro foo [bar] "baz" (bongo)) + selections: + - anchor: {line: 0, character: 30} + active: {line: 0, character: 30} + marks: {} +finalState: + documentContents: (defmacro [bar] "baz" (bongo)) + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + thatMark: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: name, includeSiblings: false}}] diff --git a/src/util/nodeSelectors.ts b/src/util/nodeSelectors.ts index a12c74e11a..c77fc1150e 100644 --- a/src/util/nodeSelectors.ts +++ b/src/util/nodeSelectors.ts @@ -201,6 +201,19 @@ export function delimitersSelector(...delimiters: string[]) { return delimitedSelector((node) => delimiters.includes(node.type), ", "); } +/** + * Creates a selector which can be used to automatically clean up after elements + * in a list by removing leading or trailing delimiters + * @param isDelimiterNode A function used to determine whether a given node is a + * delimiter node + * @param defaultDelimiter The default list separator to use if we can't + * determine it by looking before or after the given node + * @param getStartNode A function to be applied to the node to determine which + * node is the start node if we really want to expand to a sequence of nodes + * @param getEndNode A function to be applied to the node to determine which + * node is the end node if we really want to expand to a sequence of nodes + * @returns A selection extractor + */ export function delimitedSelector( isDelimiterNode: (node: SyntaxNode) => boolean, defaultDelimiter: string, From 66a1982900742f7049f72f804f45b8626846eaa4 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 16 Dec 2021 11:27:01 +0000 Subject: [PATCH 8/9] A bit of cleanup --- src/languages/clojure.ts | 12 +++++++----- src/languages/csharp.ts | 12 ++++++------ src/util/nodeFinders.ts | 21 +++++++++++++++++++++ src/util/nodeMatchers.ts | 25 +++++++++++++++++-------- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index 6ac21782dc..06e166836e 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -1,5 +1,6 @@ import { cascadingMatcher, + chainedMatcher, createPatternMatchers, matcher, patternMatcher, @@ -118,11 +119,10 @@ function functionNameBasedMatcher(...names: string[]) { const functionFinder = functionNameBasedFinder("defn", "defmacro"); -const functionNameMatcher = matcher( - flow(functionFinder, (functionNode) => - functionNode == null ? null : getValueNodes(functionNode)[1] - ) -); +const functionNameMatcher = chainedMatcher([ + functionFinder, + (functionNode) => getValueNodes(functionNode)[1], +]); const nodeMatchers: Partial> = { comment: "comment", @@ -171,6 +171,8 @@ const nodeMatchers: Partial> = { functionNameBasedMatcher("fn"), patternMatcher("anon_fn_lit") ), + + ifStatement: functionNameBasedMatcher("fn"), }; export default createPatternMatchers(nodeMatchers); diff --git a/src/languages/csharp.ts b/src/languages/csharp.ts index e9e7f77f4a..4d91f3a473 100644 --- a/src/languages/csharp.ts +++ b/src/languages/csharp.ts @@ -1,7 +1,7 @@ import { SyntaxNode } from "web-tree-sitter"; import { cascadingMatcher, - composedMatcher, + chainedMatcher, createPatternMatchers, matcher, trailingMatcher, @@ -162,26 +162,26 @@ const makeDelimitedSelector = (leftType: string, rightType: string) => const getMapMatchers = { map: cascadingMatcher( - composedMatcher([ + chainedMatcher([ typedNodeFinder(...OBJECT_TYPES_WITH_INITIALIZERS_AS_CHILDREN), getChildInitializerNode, ]), - composedMatcher([ + chainedMatcher([ typedNodeFinder("object_creation_expression"), getInitializerNode, ]) ), - collectionKey: composedMatcher([ + collectionKey: chainedMatcher([ typedNodeFinder("assignment_expression"), (node: SyntaxNode) => node.childForFieldName("left"), ]), value: matcher((node: SyntaxNode) => node.childForFieldName("right")), list: cascadingMatcher( - composedMatcher([ + chainedMatcher([ typedNodeFinder(...LIST_TYPES_WITH_INITIALIZERS_AS_CHILDREN), getChildInitializerNode, ]), - composedMatcher([ + chainedMatcher([ typedNodeFinder("object_creation_expression"), (node: SyntaxNode) => node.childForFieldName("initializer"), ]) diff --git a/src/util/nodeFinders.ts b/src/util/nodeFinders.ts index 02550b83d1..dc0aabd9a6 100644 --- a/src/util/nodeFinders.ts +++ b/src/util/nodeFinders.ts @@ -10,6 +10,27 @@ export const nodeFinder = ( }; }; +/** + * Given a list of node finders returns a new node finder which applies them in + * sequence returning null if any of the sequence returns null otherwise + * returning the output of the final node finder + * @param nodeFinders A list of node finders to apply in sequence + * @returns A node finder which is a chain of the input node finders + */ +export function chainedNodeFinder(...nodeFinders: NodeFinder[]) { + return (node: SyntaxNode) => { + let currentNode: SyntaxNode | null = node; + for (const nodeFinder of nodeFinders) { + currentNode = nodeFinder(currentNode); + if (currentNode == null) { + return null; + } + } + + return currentNode; + }; +} + export const typedNodeFinder = (...typeNames: string[]): NodeFinder => { return nodeFinder((node) => typeNames.includes(node.type)); }; diff --git a/src/util/nodeMatchers.ts b/src/util/nodeMatchers.ts index dee1d700e9..f58363ac74 100644 --- a/src/util/nodeMatchers.ts +++ b/src/util/nodeMatchers.ts @@ -18,6 +18,7 @@ import { typedNodeFinder, patternFinder, argumentNodeFinder, + chainedNodeFinder, } from "./nodeFinders"; export function matcher( @@ -37,19 +38,27 @@ export function matcher( }; } -export function composedMatcher( +/** + * Given a list of node finders returns a matcher which applies them in + * sequence returning null if any of the sequence returns null otherwise + * returning the output of the final node finder + * @param nodeFinders A list of node finders to apply in sequence + * @param selector The selector to apply to the final node + * @returns A matcher which is a chain of the input node finders + */ +export function chainedMatcher( finders: NodeFinder[], selector: SelectionExtractor = simpleSelectionExtractor ): NodeMatcher { + const nodeFinder = chainedNodeFinder(...finders); + return function (selection: SelectionWithEditor, initialNode: SyntaxNode) { - let returnNode: SyntaxNode = initialNode; - for (const finder of finders) { - const foundNode = finder(returnNode, selection.selection); - if (foundNode == null) { - return null; - } - returnNode = foundNode; + const returnNode = nodeFinder(initialNode); + + if (returnNode == null) { + return null; } + return [ { node: returnNode, From e5541018d53fc9c0a1d1de214762c814e7b75669 Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Thu, 16 Dec 2021 16:15:19 +0000 Subject: [PATCH 9/9] Support if statements --- src/languages/clojure.ts | 16 ++++++++++++- .../languages/clojure/clearCondition.yml | 23 +++++++++++++++++++ .../languages/clojure/clearIfState.yml | 23 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearCondition.yml create mode 100644 src/test/suite/fixtures/recorded/languages/clojure/clearIfState.yml diff --git a/src/languages/clojure.ts b/src/languages/clojure.ts index 06e166836e..52ff55324f 100644 --- a/src/languages/clojure.ts +++ b/src/languages/clojure.ts @@ -124,6 +124,15 @@ const functionNameMatcher = chainedMatcher([ (functionNode) => getValueNodes(functionNode)[1], ]); +const ifStatementFinder = functionNameBasedFinder( + "if", + "if-let", + "when", + "when-let" +); + +const ifStatementMatcher = matcher(ifStatementFinder); + const nodeMatchers: Partial> = { comment: "comment", map: "map_lit", @@ -172,7 +181,12 @@ const nodeMatchers: Partial> = { patternMatcher("anon_fn_lit") ), - ifStatement: functionNameBasedMatcher("fn"), + ifStatement: ifStatementMatcher, + + condition: chainedMatcher([ + ifStatementFinder, + (node) => getValueNodes(node)[1], + ]), }; export default createPatternMatchers(nodeMatchers); diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearCondition.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearCondition.yml new file mode 100644 index 0000000000..95c1224bea --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearCondition.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear condition + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: condition, includeSiblings: false} +initialState: + documentContents: (if true "hello") + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + marks: {} +finalState: + documentContents: (if "hello") + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + thatMark: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: condition, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/clojure/clearIfState.yml b/src/test/suite/fixtures/recorded/languages/clojure/clearIfState.yml new file mode 100644 index 0000000000..8161fb3c4b --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/clojure/clearIfState.yml @@ -0,0 +1,23 @@ +languageId: clojure +command: + version: 1 + spokenForm: clear if state + action: clearAndSetSelection + targets: + - type: primitive + modifier: {type: containingScope, scopeType: ifStatement, includeSiblings: false} +initialState: + documentContents: (if true "hello") + selections: + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: containingScope, scopeType: ifStatement, includeSiblings: false}}]