diff --git a/src/languages/cpp.ts b/src/languages/cpp.ts index 8358f889f5..b4d8fb3949 100644 --- a/src/languages/cpp.ts +++ b/src/languages/cpp.ts @@ -1,7 +1,7 @@ import { createPatternMatchers, argumentMatcher, - valueMatcher, + prefixedMatcher, } from "../util/nodeMatchers"; import { NodeMatcherAlternative, ScopeType } from "../typings/Types"; @@ -95,7 +95,7 @@ const nodeMatchers: Partial> = { "function_definition[declarator][declarator]", // void funcName() {} "declaration.function_declarator![declarator]", // void funcName(); ], - value: valueMatcher( + value: prefixedMatcher( "*[declarator][value]", "*[value]", "assignment_expression[right]", diff --git a/src/languages/java.ts b/src/languages/java.ts index 965b487204..fb3921b013 100644 --- a/src/languages/java.ts +++ b/src/languages/java.ts @@ -1,7 +1,7 @@ import { createPatternMatchers, argumentMatcher, - valueMatcher, + prefixedMatcher, } from "../util/nodeMatchers"; import { NodeMatcherAlternative, ScopeType } from "../typings/Types"; @@ -62,7 +62,7 @@ const nodeMatchers: Partial> = { "method_declaration.identifier!", "constructor_declaration.identifier!", ], - value: valueMatcher("*[declarator][value]", "*[value]"), + value: prefixedMatcher("*[declarator][value]", "*[value]"), collectionItem: argumentMatcher("array_initializer"), argumentOrParameter: argumentMatcher("formal_parameters", "argument_list"), }; diff --git a/src/languages/json.ts b/src/languages/json.ts index d2b72a301a..ca902edaf0 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -1,7 +1,8 @@ import { createPatternMatchers, argumentMatcher, - valueMatcher, + prefixedMatcher, + suffixedMatcher, } from "../util/nodeMatchers"; import { ScopeType, NodeMatcherAlternative } from "../typings/Types"; @@ -10,8 +11,8 @@ const nodeMatchers: Partial> = { list: "array", string: "string", comment: "comment", - collectionKey: "pair[key]", - value: valueMatcher("*[value]"), + collectionKey: suffixedMatcher("pair[key]"), + value: prefixedMatcher("*[value]"), collectionItem: argumentMatcher("object", "array"), }; diff --git a/src/languages/python.ts b/src/languages/python.ts index 063bef283b..5f9595c773 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -2,7 +2,8 @@ import { SyntaxNode } from "web-tree-sitter"; import { createPatternMatchers, argumentMatcher, - valueMatcher, + prefixedMatcher, + suffixedMatcher, } from "../util/nodeMatchers"; import { NodeMatcherAlternative, ScopeType } from "../typings/Types"; @@ -46,7 +47,7 @@ const nodeMatchers: Partial> = { list: listTypes, statement: STATEMENT_TYPES, string: "string", - collectionKey: "pair[key]", + collectionKey: suffixedMatcher("pair[key]"), ifStatement: "if_statement", anonymousFunction: "lambda", functionCall: "call", @@ -55,7 +56,7 @@ const nodeMatchers: Partial> = { className: "class_definition[name]", namedFunction: "decorated_definition?.function_definition", functionName: "function_definition[name]", - type: valueMatcher("function_definition[return_type]", "*[type]"), + type: prefixedMatcher("function_definition[return_type]", "*[type]"), name: [ "assignment[left]", "typed_parameter.identifier!", @@ -63,7 +64,7 @@ const nodeMatchers: Partial> = { "*[name]", ], collectionItem: argumentMatcher(...dictionaryTypes, ...listTypes), - value: valueMatcher("assignment[right]", "~subscript[value]"), + value: prefixedMatcher("assignment[right]", "~subscript[value]"), argumentOrParameter: argumentMatcher("parameters", "argument_list"), }; diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index ac3ad463d0..fc83c7fe55 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -5,6 +5,7 @@ import { patternMatcher, createPatternMatchers, argumentMatcher, + suffixedMatcher, } from "../util/nodeMatchers"; import { NodeMatcherAlternative, @@ -86,7 +87,10 @@ const nodeMatchers: Partial> = { map: mapTypes, list: listTypes, string: ["string", "template_string"], - collectionKey: ["pair[key]", "jsx_attribute.property_identifier!"], + collectionKey: suffixedMatcher( + "pair[key]", + "jsx_attribute.property_identifier!" + ), collectionItem: argumentMatcher(...mapTypes, ...listTypes), value: valueMatcher(), ifStatement: "if_statement", diff --git a/src/test/suite/fixtures/recorded/languages/python/chuckKey.yml b/src/test/suite/fixtures/recorded/languages/python/chuckKey.yml new file mode 100644 index 0000000000..308f9bfe12 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/python/chuckKey.yml @@ -0,0 +1,31 @@ +spokenForm: chuck key +languageId: python +command: + actionName: remove + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + { + "foo": "bar", + "baz": "bongo", + } + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} +finalState: + documentContents: |- + { + "bar", + "baz": "bongo", + } + 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: outside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/python/chuckKey2.yml b/src/test/suite/fixtures/recorded/languages/python/chuckKey2.yml new file mode 100644 index 0000000000..a79d64f4a7 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/python/chuckKey2.yml @@ -0,0 +1,31 @@ +spokenForm: chuck key +languageId: python +command: + actionName: remove + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + { + "foo": "bar", + "baz": "bongo", + } + selections: + - anchor: {line: 1, character: 12} + active: {line: 1, character: 12} +finalState: + documentContents: |- + { + "bar", + "baz": "bongo", + } + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/python/chuckValue.yml b/src/test/suite/fixtures/recorded/languages/python/chuckValue.yml index 1eef01799f..7c637d1b57 100644 --- a/src/test/suite/fixtures/recorded/languages/python/chuckValue.yml +++ b/src/test/suite/fixtures/recorded/languages/python/chuckValue.yml @@ -13,11 +13,11 @@ initialState: - anchor: {line: 0, character: 2} active: {line: 0, character: 2} finalState: - documentContents: "foo: str " + documentContents: "foo: str" selections: - anchor: {line: 0, character: 2} active: {line: 0, character: 2} thatMark: - - anchor: {line: 0, character: 9} - active: {line: 0, character: 9} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: outside, modifier: {type: containingScope, scopeType: value, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/chuckKey.yml b/src/test/suite/fixtures/recorded/languages/typescript/chuckKey.yml new file mode 100644 index 0000000000..337236886d --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/chuckKey.yml @@ -0,0 +1,29 @@ +spokenForm: chuck key +languageId: typescript +command: + actionName: remove + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + { + foo: "bar" + } + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} +finalState: + documentContents: |- + { + "bar" + } + 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: outside, modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false}}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/clearKey.yml b/src/test/suite/fixtures/recorded/languages/typescript/clearKey.yml new file mode 100644 index 0000000000..824f0456a0 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/clearKey.yml @@ -0,0 +1,29 @@ +spokenForm: clear key +languageId: typescript +command: + actionName: clearAndSetSelection + partialTargets: + - type: primitive + modifier: {type: containingScope, scopeType: collectionKey, includeSiblings: false} + extraArgs: [] +marks: {} +initialState: + documentContents: |- + { + foo: "bar" + } + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} +finalState: + documentContents: |- + { + : "bar" + } + 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: collectionKey, includeSiblings: false}}] diff --git a/src/util/nodeMatchers.ts b/src/util/nodeMatchers.ts index ee3d4dd69d..e61a57a0bd 100644 --- a/src/util/nodeMatchers.ts +++ b/src/util/nodeMatchers.ts @@ -11,6 +11,7 @@ import { simpleSelectionExtractor, argumentSelectionExtractor, selectWithLeadingDelimiter, + selectWithTrailingDelimiter, } from "./nodeSelectors"; import { typedNodeFinder, @@ -72,10 +73,26 @@ export function argumentMatcher(...parentTypes: string[]): NodeMatcher { ); } -export function valueMatcher(...patterns: string[]): NodeMatcher { +/** + * Given `patterns`, creates a node matcher that will add leading delimiter to + * removal range. + * @param patterns Patterns for pattern finder + * @returns A node matcher + */ +export function prefixedMatcher(...patterns: string[]): NodeMatcher { return matcher(patternFinder(...patterns), selectWithLeadingDelimiter); } +/** + * Given `patterns`, creates a node matcher that will add trailing delimiter to + * removal range. + * @param patterns Patterns for pattern finder + * @returns A node matcher + */ +export function suffixedMatcher(...patterns: string[]): NodeMatcher { + return matcher(patternFinder(...patterns), selectWithTrailingDelimiter); +} + /** * Create a new matcher that will try the given matchers in sequence until one * returns non-null diff --git a/src/util/nodeSelectors.ts b/src/util/nodeSelectors.ts index 00c028b271..e24262eba1 100644 --- a/src/util/nodeSelectors.ts +++ b/src/util/nodeSelectors.ts @@ -40,11 +40,11 @@ export function selectWithLeadingDelimiter( editor: TextEditor, node: SyntaxNode ): SelectionWithContext { - const leadingDelimiterToken = node.previousSibling; + const leadingNonDelimiterToken = node.previousSibling?.previousSibling; const leadingDelimiterRange = - leadingDelimiterToken != null - ? makeRange(leadingDelimiterToken.startPosition, node.startPosition) + leadingNonDelimiterToken != null + ? makeRange(leadingNonDelimiterToken.endPosition, node.startPosition) : null; return { @@ -55,6 +55,25 @@ export function selectWithLeadingDelimiter( }; } +export function selectWithTrailingDelimiter( + editor: TextEditor, + node: SyntaxNode +): SelectionWithContext { + const trailingNonDelimiterToken = node.nextSibling?.nextSibling; + + const trailingDelimiterRange = + trailingNonDelimiterToken != null + ? makeRange(node.endPosition, trailingNonDelimiterToken.startPosition) + : null; + + return { + ...simpleSelectionExtractor(editor, node), + context: { + trailingDelimiterRange, + }, + }; +} + function getNextNonDelimiterNode( startNode: SyntaxNode, isDelimiterNode: (node: SyntaxNode) => boolean