diff --git a/src/languages/cpp.ts b/src/languages/cpp.ts index b8b4879e96..c4689de387 100644 --- a/src/languages/cpp.ts +++ b/src/languages/cpp.ts @@ -113,7 +113,6 @@ const nodeMatchers: Partial< ], [":", "=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>="] ), - collectionItem: argumentMatcher("initializer_list"), argumentOrParameter: argumentMatcher("parameter_list", "argument_list"), attribute: "attribute", }; diff --git a/src/languages/csharp.ts b/src/languages/csharp.ts index 42bcbb08e9..8817ac2eb3 100644 --- a/src/languages/csharp.ts +++ b/src/languages/csharp.ts @@ -16,69 +16,6 @@ import { nodeFinder, typedNodeFinder } from "../util/nodeFinders"; import { delimitedSelector, childRangeSelector } from "../util/nodeSelectors"; import { patternFinder } from "../util/nodeFinders"; -// Generated by the following command: -// > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-c-sharp/master/src/node-types.json \ -// | jq '.[] | select(.type == "_expression") | [.subtypes[].type]' -const EXPRESSION_TYPES = [ - "anonymous_method_expression", - "anonymous_object_creation_expression", - "array_creation_expression", - "as_expression", - "assignment_expression", - "await_expression", - "base_expression", - "binary_expression", - "boolean_literal", - "cast_expression", - "character_literal", - "checked_expression", - "conditional_access_expression", - "conditional_expression", - "default_expression", - "element_access_expression", - "element_binding_expression", - "generic_name", - "global", - "identifier", - "implicit_array_creation_expression", - "implicit_object_creation_expression", - "implicit_stack_alloc_array_creation_expression", - "initializer_expression", - "integer_literal", - "interpolated_string_expression", - "invocation_expression", - "is_expression", - "is_pattern_expression", - "lambda_expression", - "make_ref_expression", - "member_access_expression", - "null_literal", - "object_creation_expression", - "parenthesized_expression", - "postfix_unary_expression", - "prefix_unary_expression", - "query_expression", - "range_expression", - "real_literal", - "ref_expression", - "ref_type_expression", - "ref_value_expression", - "size_of_expression", - "stack_alloc_array_creation_expression", - "string_literal", - "switch_expression", - "this_expression", - "throw_expression", - "tuple_expression", - "type_of_expression", - "verbatim_string_literal", - "with_expression", -]; - -function isExpression(node: SyntaxNode) { - return EXPRESSION_TYPES.includes(node.type); -} - // Generated by the following command: // > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-c-sharp/master/src/node-types.json \ // | jq '.[] | select(.type == "_statement" or .type == "_declaration") | [.subtypes[].type]' @@ -197,23 +134,6 @@ const getMapMatchers = { (node: SyntaxNode) => node.childForFieldName("initializer"), ]) ), - collectionItem: matcher( - nodeFinder( - (node) => - (node.parent?.type === "initializer_expression" && - isExpression(node)) || - node.type === "assignment_expression" - ), - delimitedSelector( - (node) => - node.type === "," || - node.type === "[" || - node.type === "]" || - node.type === "}" || - node.type === "{", - ", " - ) - ), string: typeMatcher("string_literal"), }; diff --git a/src/languages/go.ts b/src/languages/go.ts index 477c652133..e26aae2320 100644 --- a/src/languages/go.ts +++ b/src/languages/go.ts @@ -64,7 +64,6 @@ const nodeMatchers: Partial< patternMatcher("parameter_declaration"), patternMatcher("argument_declaration") ), - collectionItem: ["keyed_element", "element"], collectionKey: "keyed_element[0]", value: cascadingMatcher( patternMatcher("keyed_element[1]"), diff --git a/src/languages/java.ts b/src/languages/java.ts index 4df9d82a3b..9abe0bf303 100644 --- a/src/languages/java.ts +++ b/src/languages/java.ts @@ -110,7 +110,6 @@ const nodeMatchers: Partial< ["=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>="] ), condition: conditionMatcher("*[condition]"), - collectionItem: argumentMatcher("array_initializer"), argumentOrParameter: argumentMatcher("formal_parameters", "argument_list"), }; diff --git a/src/languages/json.ts b/src/languages/json.ts index 23c69c0718..b32645a88a 100644 --- a/src/languages/json.ts +++ b/src/languages/json.ts @@ -1,12 +1,11 @@ +import { SyntaxNode } from "web-tree-sitter"; +import { SimpleScopeTypeType } from "../typings/targetDescriptor.types"; +import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types"; import { createPatternMatchers, - argumentMatcher, leadingMatcher, trailingMatcher, } from "../util/nodeMatchers"; -import { NodeMatcherAlternative, SelectionWithEditor } from "../typings/Types"; -import { SimpleScopeTypeType } from "../typings/targetDescriptor.types"; -import { SyntaxNode } from "web-tree-sitter"; import { getNodeRange } from "../util/nodeSelectors"; const nodeMatchers: Partial< @@ -17,7 +16,6 @@ const nodeMatchers: Partial< string: "string", collectionKey: trailingMatcher(["pair[key]"], [":"]), value: leadingMatcher(["*[value]"], [":"]), - collectionItem: argumentMatcher("object", "array"), }; export const patternMatchers = createPatternMatchers(nodeMatchers); diff --git a/src/languages/php.ts b/src/languages/php.ts index f9e5b629fc..4c6e633521 100644 --- a/src/languages/php.ts +++ b/src/languages/php.ts @@ -145,7 +145,6 @@ const nodeMatchers: Partial< ), collectionKey: trailingMatcher(["array_element_initializer[0]"], ["=>"]), - collectionItem: argumentMatcher("array_creation_expression"), argumentOrParameter: argumentMatcher("arguments", "formal_parameters"), }; diff --git a/src/languages/python.ts b/src/languages/python.ts index 264f40d99d..9c636b58f8 100644 --- a/src/languages/python.ts +++ b/src/languages/python.ts @@ -1,18 +1,22 @@ +import { Selection } from "vscode"; import { SyntaxNode } from "web-tree-sitter"; +import { SimpleScopeTypeType } from "../typings/targetDescriptor.types"; +import { NodeFinder, NodeMatcherAlternative } from "../typings/Types"; +import { argumentNodeFinder, patternFinder } from "../util/nodeFinders"; import { - createPatternMatchers, argumentMatcher, - leadingMatcher, - trailingMatcher, cascadingMatcher, - patternMatcher, conditionMatcher, + createPatternMatchers, + leadingMatcher, matcher, + patternMatcher, + trailingMatcher, } from "../util/nodeMatchers"; -import { patternFinder } from "../util/nodeFinders"; -import { NodeMatcherAlternative } from "../typings/Types"; -import { SimpleScopeTypeType } from "../typings/targetDescriptor.types"; -import { childRangeSelector } from "../util/nodeSelectors"; +import { + argumentSelectionExtractor, + childRangeSelector, +} from "../util/nodeSelectors"; // Generated by the following command: // > curl https://raw.githubusercontent.com/tree-sitter/tree-sitter-python/d6210ceab11e8d812d4ab59c07c81458ec6e5184/src/node-types.json \ @@ -49,6 +53,21 @@ export const getTypeNode = (node: SyntaxNode) => const dictionaryTypes = ["dictionary", "dictionary_comprehension"]; const listTypes = ["list", "list_comprehension", "set"]; +function importNodeFinder(): NodeFinder { + const finder = argumentNodeFinder("import_from_statement"); + return (node: SyntaxNode, selection?: Selection) => { + const childNode = finder(node, selection); + if ( + childNode?.type === "dotted_name" && + childNode.id !== childNode.parent?.firstNamedChild?.id + ) { + return childNode; + } + + return null; + }; +} + const nodeMatchers: Partial< Record > = { @@ -56,6 +75,7 @@ const nodeMatchers: Partial< list: listTypes, statement: STATEMENT_TYPES, string: "string", + collectionItem: matcher(importNodeFinder(), argumentSelectionExtractor()), collectionKey: trailingMatcher(["pair[key]"], [":"]), ifStatement: "if_statement", anonymousFunction: "lambda?.lambda", @@ -78,7 +98,6 @@ const nodeMatchers: Partial< "parameters.identifier!", "*[name]", ], - collectionItem: argumentMatcher(...dictionaryTypes, ...listTypes), value: cascadingMatcher( leadingMatcher( ["assignment[right]", "augmented_assignment[right]", "~subscript[value]"], diff --git a/src/languages/typescript.ts b/src/languages/typescript.ts index 362ee76992..c2a5b3731c 100644 --- a/src/languages/typescript.ts +++ b/src/languages/typescript.ts @@ -173,7 +173,6 @@ const nodeMatchers: Partial< ], [":"] ), - collectionItem: argumentMatcher(...mapTypes, ...listTypes), value: cascadingMatcher( valueMatcher(), patternMatcher("return_statement.~return!"), diff --git a/src/processTargets/getModifierStage.ts b/src/processTargets/getModifierStage.ts index ff7af44716..b7af447cbb 100644 --- a/src/processTargets/getModifierStage.ts +++ b/src/processTargets/getModifierStage.ts @@ -10,6 +10,7 @@ import { ExcludeInteriorStage, InteriorOnlyStage, } from "./modifiers/InteriorStage"; +import ItemStage from "./modifiers/ItemStage"; import { LeadingStage, TrailingStage } from "./modifiers/LeadingTrailingStages"; import ModifyIfWeakStage from "./modifiers/ModifyIfWeakStage"; import OrdinalRangeSubTokenStage, { @@ -98,6 +99,8 @@ const getContainingScopeStage = ( ); case "url": return new UrlStage(modifier as UrlModifier); + case "collectionItem": + return new ItemStage(modifier); case "surroundingPair": return new SurroundingPairStage( modifier as ContainingSurroundingPairModifier diff --git a/src/processTargets/modifiers/ItemStage/ItemStage.ts b/src/processTargets/modifiers/ItemStage/ItemStage.ts new file mode 100644 index 0000000000..bbf3cf4212 --- /dev/null +++ b/src/processTargets/modifiers/ItemStage/ItemStage.ts @@ -0,0 +1,171 @@ +import { Range, TextEditor } from "vscode"; +import { NoContainingScopeError } from "../../../errors"; +import { Target } from "../../../typings/target.types"; +import { + ContainingScopeModifier, + EveryScopeModifier, + SimpleScopeTypeType, +} from "../../../typings/targetDescriptor.types"; +import { ProcessedTargetsContext } from "../../../typings/Types"; +import { getInsertionDelimiter } from "../../../util/nodeSelectors"; +import { ModifierStage } from "../../PipelineStages.types"; +import ScopeTypeTarget from "../../targets/ScopeTypeTarget"; +import ContainingSyntaxScopeStage, { + SimpleContainingScopeModifier, +} from "../scopeTypeStages/ContainingSyntaxScopeStage"; +import { getIterationScope } from "./getIterationScope"; +import { tokenizeRange } from "./tokenizeRange"; + +export default class ItemStage implements ModifierStage { + constructor(private modifier: ContainingScopeModifier | EveryScopeModifier) {} + + run(context: ProcessedTargetsContext, target: Target): Target[] { + // First try the language specific implementation of item + try { + return new ContainingSyntaxScopeStage( + this.modifier + ).run(context, target); + } catch (_error) {} + + // Then try the textual implementation + if (this.modifier.type === "everyScope") { + return this.getEveryTarget(context, target); + } + return [this.getSingleTarget(context, target)]; + } + + private getEveryTarget(context: ProcessedTargetsContext, target: Target) { + const itemInfos = getItemInfosForIterationScope(context, target); + + // If weak expand to all items in iteration scope + const filteredItemInfos = target.isWeak + ? itemInfos + : filterItemInfos(target, itemInfos); + + if (filteredItemInfos.length === 0) { + throw new NoContainingScopeError(this.modifier.scopeType.type); + } + + return filteredItemInfos.map((itemInfo) => + this.itemInfoToTarget(target, itemInfo) + ); + } + + private getSingleTarget(context: ProcessedTargetsContext, target: Target) { + const itemInfos = getItemInfosForIterationScope(context, target); + + const filteredItemInfos = filterItemInfos(target, itemInfos); + + if (filteredItemInfos.length === 0) { + throw new NoContainingScopeError(this.modifier.scopeType.type); + } + + const first = filteredItemInfos[0]; + const last = filteredItemInfos[filteredItemInfos.length - 1]; + + const itemInfo: ItemInfo = { + contentRange: first.contentRange.union(last.contentRange), + domain: first.domain.union(last.domain), + leadingDelimiterRange: first.leadingDelimiterRange, + trailingDelimiterRange: last.trailingDelimiterRange, + }; + + return this.itemInfoToTarget(target, itemInfo); + } + + private itemInfoToTarget(target: Target, itemInfo: ItemInfo) { + const delimiter = getInsertionDelimiter( + target.editor, + itemInfo.leadingDelimiterRange, + itemInfo.trailingDelimiterRange, + ", " + ); + return new ScopeTypeTarget({ + scopeTypeType: this.modifier.scopeType.type, + editor: target.editor, + isReversed: target.isReversed, + contentRange: itemInfo.contentRange, + delimiter, + leadingDelimiterRange: itemInfo.leadingDelimiterRange, + trailingDelimiterRange: itemInfo.trailingDelimiterRange, + }); + } +} + +/** Filter item infos by content range and domain intersection */ +function filterItemInfos(target: Target, itemInfos: ItemInfo[]): ItemInfo[] { + return itemInfos.filter( + (itemInfo) => itemInfo.domain.intersection(target.contentRange) != null + ); +} + +function getItemInfosForIterationScope( + context: ProcessedTargetsContext, + target: Target +) { + const { range, boundary } = getIterationScope(context, target); + return getItemsInRange(target.editor, range, boundary); +} + +function getItemsInRange( + editor: TextEditor, + interior: Range, + boundary?: [Range, Range] +): ItemInfo[] { + const tokens = tokenizeRange(editor, interior, boundary); + const itemInfos: ItemInfo[] = []; + + tokens.forEach((token, i) => { + if (token.type === "separator" || token.type === "boundary") { + return; + } + + const leadingDelimiterRange = (() => { + if (tokens[i - 2]?.type === "item") { + return new Range(tokens[i - 2].range.end, token.range.start); + } + if (tokens[i - 1]?.type === "separator") { + return new Range(tokens[i - 1].range.start, token.range.start); + } + return undefined; + })(); + + const trailingDelimiterRange = (() => { + if (tokens[i + 2]?.type === "item") { + return new Range(token.range.end, tokens[i + 2].range.start); + } + if (tokens[i + 1]?.type === "separator") { + return new Range(token.range.end, tokens[i + 1].range.end); + } + return undefined; + })(); + + // Leading boundary and separator are excluded + const domainStart = + tokens[i - 1]?.type === "boundary" || tokens[i - 1]?.type === "separator" + ? tokens[i - 1].range.end + : token.range.start; + + // Trailing boundary and separator are excluded + const domainEnd = + tokens[i + 1]?.type === "boundary" || tokens[i + 1]?.type === "separator" + ? tokens[i + 1].range.start + : token.range.end; + + itemInfos.push({ + contentRange: token.range, + leadingDelimiterRange, + trailingDelimiterRange, + domain: new Range(domainStart, domainEnd), + }); + }); + + return itemInfos; +} + +interface ItemInfo { + contentRange: Range; + leadingDelimiterRange?: Range; + trailingDelimiterRange?: Range; + domain: Range; +} diff --git a/src/processTargets/modifiers/ItemStage/getIterationScope.ts b/src/processTargets/modifiers/ItemStage/getIterationScope.ts new file mode 100644 index 0000000000..a01fd33075 --- /dev/null +++ b/src/processTargets/modifiers/ItemStage/getIterationScope.ts @@ -0,0 +1,92 @@ +import { Range, TextEditor } from "vscode"; +import { Target } from "../../../typings/target.types"; +import { ProcessedTargetsContext } from "../../../typings/Types"; +import { fitRangeToLineContent } from "../scopeTypeStages/LineStage"; +import { processSurroundingPair } from "../surroundingPair"; +import { SurroundingPairInfo } from "../surroundingPair/extractSelectionFromSurroundingPairOffsets"; + +/** + * Get the iteration scope range for item scope. + * Try to find non-string surrounding scope with a fallback to line content. + * @param context The stage process context + * @param target The stage target + * @returns The stage iteration scope and optional surrounding pair boundaries + */ +export function getIterationScope( + context: ProcessedTargetsContext, + target: Target +): { range: Range; boundary?: [Range, Range] } { + let pairInfo = getSurroundingPair( + context, + target.editor, + target.contentRange + ); + + // Iteration is necessary in case of nested strings + while (pairInfo != null) { + const stringPairInfo = getStringSurroundingPair( + context, + target.editor, + pairInfo.contentRange + ); + + // We don't look for items inside strings. + if ( + // Not in a string + stringPairInfo == null || + // In a non-string surrounding pair that is inside a surrounding string. This is fine. + stringPairInfo.contentRange.start.isBefore(pairInfo.contentRange.start) + ) { + return { + range: pairInfo.interiorRange, + boundary: pairInfo.boundary, + }; + } + + pairInfo = getParentSurroundingPair(context, target.editor, pairInfo); + } + + // We have not found a surrounding pair. Use the line. + return { + range: fitRangeToLineContent(target.editor, target.contentRange), + }; +} + +function getParentSurroundingPair( + context: ProcessedTargetsContext, + editor: TextEditor, + pairInfo: SurroundingPairInfo +) { + const startOffset = editor.document.offsetAt(pairInfo.contentRange.start); + // Can't have a parent; already at start of document + if (startOffset === 0) { + return null; + } + // Step out of this pair and see if we have a parent + const position = editor.document.positionAt(startOffset - 1); + return getSurroundingPair(context, editor, new Range(position, position)); +} + +function getSurroundingPair( + context: ProcessedTargetsContext, + editor: TextEditor, + contentRange: Range +) { + return processSurroundingPair(context, editor, contentRange, { + type: "surroundingPair", + delimiter: "collectionBoundary", + requireStrongContainment: true, + }); +} + +function getStringSurroundingPair( + context: ProcessedTargetsContext, + editor: TextEditor, + contentRange: Range +) { + return processSurroundingPair(context, editor, contentRange, { + type: "surroundingPair", + delimiter: "string", + requireStrongContainment: true, + }); +} diff --git a/src/processTargets/modifiers/ItemStage/index.ts b/src/processTargets/modifiers/ItemStage/index.ts new file mode 100644 index 0000000000..246be4d0d7 --- /dev/null +++ b/src/processTargets/modifiers/ItemStage/index.ts @@ -0,0 +1,2 @@ +import ItemStage from "./ItemStage"; +export default ItemStage; diff --git a/src/processTargets/modifiers/ItemStage/tokenizeRange.ts b/src/processTargets/modifiers/ItemStage/tokenizeRange.ts new file mode 100644 index 0000000000..52186aa22e --- /dev/null +++ b/src/processTargets/modifiers/ItemStage/tokenizeRange.ts @@ -0,0 +1,181 @@ +import { Range, TextEditor } from "vscode"; + +/** + * Given the iteration scope, returns a list of "tokens" within that collection + * In this context, we define a "token" to be either an item in the collection, + * a delimiter or a separator. For example, if {@link interior} is a range + * containing `foo(hello), bar, whatever`, and {@link boundary} consists of + * two ranges containing `(` and `)`, then we'd return the following: + * + * ```json + * [ + * { range: "(", type: "boundary" }, + * { range: "foo(hello)", type: "item" }, + * { range: ",", type: "separator" }, + * { range: "bar", type: "item" }, + * { range: ",", type: "separator" }, + * { range: "whatever", type: "item" }, + * { range: ")", type: "boundary" }, + * ] + * ``` + * + * Where each `range` isn't actually a string, but a range whose text is the + * given string. + * @param editor The editor containing the range + * @param interior The range to look for tokens within + * @param boundary Optional boundaries for collections. [], {} + * @returns List of tokens + */ +export function tokenizeRange( + editor: TextEditor, + interior: Range, + boundary?: [Range, Range] +): Token[] { + const { document } = editor; + const text = document.getText(interior); + /** + * The interior range tokenized into delimited regions, including the delimiters themselves. For example: + * `"foo(hello), bar, whatever"` => + * `["foo", "(", "hello", ")", ",", " bar", ",", " whatever"]` + */ + const lexemes = text + // NB: Both the delimiters and the text between them are included because we + // use a capture group in this split regex + .split(/([,(){}<>[\]"'`]|\\"|\\'|\\`)/g) + .filter((lexeme) => lexeme.length > 0); + const joinedLexemes = joinLexemesBySkippingMatchingPairs(lexemes); + const tokens: Token[] = []; + let offset = document.offsetAt(interior.start); + + joinedLexemes.forEach((lexeme) => { + // Whitespace found. Just skip + if (lexeme.trim().length === 0) { + offset += lexeme.length; + return; + } + + // Separator delimiter found. + if (lexeme === separator) { + tokens.push({ + type: "separator", + range: new Range( + document.positionAt(offset), + document.positionAt(offset + lexeme.length) + ), + }); + } + + // Text/item content found + else { + const offsetStart = offset + (lexeme.length - lexeme.trimStart().length); + tokens.push({ + type: "item", + range: new Range( + document.positionAt(offsetStart), + document.positionAt(offsetStart + lexeme.trim().length) + ), + }); + } + + offset += lexeme.length; + }); + + if (boundary != null) { + return [ + { type: "boundary", range: boundary[0] }, + ...tokens, + { type: "boundary", range: boundary[1] }, + ]; + } + + return tokens; +} + +/** + * Takes a list of lexemes and joins them into a list of alternating items and separators, skipping matching pairs (), {}, etc + * @param lexemes List of lexemes to operate on + * @returns List of merged lexemes. Note that its length will be less than or equal to {@link lexemes} + */ +export function joinLexemesBySkippingMatchingPairs(lexemes: string[]) { + const result: string[] = []; + /** + * The number of left delimiters minus right delimiters we've seen. If the + * balance is 0, we're at the top level of the collection, so separators are + * relevant. Otherwise we ignore separators because they're nested + */ + let delimiterBalance = 0; + /** The most recent opening delimiter we've seen */ + let openingDelimiter: string | null = null; + /** The closing delimiter we're currently looking for */ + let closingDelimiter: string | null = null; + /** + * The index in {@link lexemes} of the first lexeme in the current token we're + * merging. + */ + let startIndex: number = -1; + + lexemes.forEach((lexeme, index) => { + if (delimiterBalance > 0) { + // We are waiting for a closing delimiter + if (lexeme === closingDelimiter) { + // Closing delimiter found + --delimiterBalance; + } + // Additional opening delimiter found + else if (lexeme === openingDelimiter) { + ++delimiterBalance; + } + } + + // Starting delimiter found + else if (leftToRightMap[lexeme] != null) { + openingDelimiter = lexeme; + closingDelimiter = leftToRightMap[lexeme]; + delimiterBalance = 1; + if (startIndex < 0) { + // This is the first lexeme to be joined + startIndex = index; + } + } + + // This is the first lexeme to be joined + else if (startIndex < 0) { + startIndex = index; + } + + const isSeparator = lexeme === separator && delimiterBalance === 0; + + if (isSeparator || index === lexemes.length - 1) { + // This is the last lexeme to be joined + const endIndex = isSeparator ? index : index + 1; + result.push(lexemes.slice(startIndex, endIndex).join("")); + startIndex = -1; + if (isSeparator) { + // Add the separator itself + result.push(lexeme); + } + } + }); + + return result; +} + +const separator = ","; + +// Mapping between opening and closing delimiters +/* eslint-disable @typescript-eslint/naming-convention */ +const leftToRightMap: { [key: string]: string } = { + "(": ")", + "{": "}", + "<": ">", + "[": "]", + '"': '"', + "'": "'", + "`": "`", +}; +/* eslint-enable @typescript-eslint/naming-convention */ + +interface Token { + range: Range; + type: "item" | "separator" | "boundary"; +} diff --git a/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts b/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts index b0d69574c9..a51e7e7723 100644 --- a/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts +++ b/src/processTargets/modifiers/surroundingPair/delimiterMaps.ts @@ -39,4 +39,10 @@ export const complexDelimiterMap: Record< > = { any: Object.keys(delimiterToText), string: ["singleQuotes", "doubleQuotes", "backtickQuotes"], + collectionBoundary: [ + "parentheses", + "squareBrackets", + "curlyBrackets", + "angleBrackets", + ], }; diff --git a/src/test/suite/fixtures/recorded/itemTextual/chuckEveryItem.yml b/src/test/suite/fixtures/recorded/itemTextual/chuckEveryItem.yml new file mode 100644 index 0000000000..0a5d3c0fb0 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/chuckEveryItem.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + spokenForm: chuck every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: remove} +initialState: + documentContents: |- + { + "hello": "there", + "testing": "whatever", + } + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + marks: {} +finalState: + documentContents: |- + { + + } + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/chuckItem.yml b/src/test/suite/fixtures/recorded/itemTextual/chuckItem.yml new file mode 100644 index 0000000000..68e3fadbed --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/chuckItem.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + spokenForm: chuck item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: remove} +initialState: + documentContents: |- + [ + aaa, + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: |- + [ + + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/chuckItem2.yml b/src/test/suite/fixtures/recorded/itemTextual/chuckItem2.yml new file mode 100644 index 0000000000..102aeed0a8 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/chuckItem2.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + spokenForm: chuck item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: remove} +initialState: + documentContents: |- + [ + aaa + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: |- + [ + + ] + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + thatMark: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem.yml new file mode 100644 index 0000000000..7463059ead --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem.yml @@ -0,0 +1,44 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + 111, + "a\"b\"c", + 222, + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: |- + [ + , + , + , + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem2.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem2.yml new file mode 100644 index 0000000000..1016795f85 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem2.yml @@ -0,0 +1,34 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: aaa, bbb, ccc + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ", , " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem3.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem3.yml new file mode 100644 index 0000000000..719b08bea2 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem3.yml @@ -0,0 +1,30 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: aaa, """bbb""" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ", " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem4.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem4.yml new file mode 100644 index 0000000000..a469793ecd --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem4.yml @@ -0,0 +1,38 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: a, "b\",b\"b", 'c\',c\'c', `d\`,d\`d` + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ", , , " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem5.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem5.yml new file mode 100644 index 0000000000..551b9822b1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem5.yml @@ -0,0 +1,46 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + a, + [ + b + ], + c + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + marks: {} +finalState: + documentContents: |- + [ + , + , + + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem6.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem6.yml new file mode 100644 index 0000000000..9dd42158c4 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem6.yml @@ -0,0 +1,34 @@ +languageId: plaintext +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + a, b, + c, d + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |- + , , + c, d + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem7.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem7.yml new file mode 100644 index 0000000000..c9e2e9c255 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItem7.yml @@ -0,0 +1,38 @@ +languageId: typescript +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: seq('(', ",", $._import_list, ')') + selections: + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + marks: {} +finalState: + documentContents: seq(, , , ) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + thatMark: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemBlock.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemBlock.yml new file mode 100644 index 0000000000..7751b69200 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemBlock.yml @@ -0,0 +1,44 @@ +languageId: plaintext +command: + spokenForm: clear every item block + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + - type: containingScope + scopeType: {type: paragraph} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + a, b, + c, d + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |- + , , + , + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + thatMark: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}, {type: containingScope, scopeType: {type: paragraph}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemToken.yml b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemToken.yml new file mode 100644 index 0000000000..bd23dfa8cf --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearEveryItemToken.yml @@ -0,0 +1,28 @@ +languageId: plaintext +command: + spokenForm: clear every item token + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + - type: containingScope + scopeType: {type: token} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: aaa bbb, ccc + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ", ccc" + 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}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}, {type: containingScope, scopeType: {type: token}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem.yml new file mode 100644 index 0000000000..b15f7288e1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + 111, + "a\"b\"c", + 222, + ] + selections: + - anchor: {line: 2, character: 5} + active: {line: 2, character: 5} + marks: {} +finalState: + documentContents: |- + [ + 111, + , + 222, + ] + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + thatMark: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem10.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem10.yml new file mode 100644 index 0000000000..1ee4b4228f --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem10.yml @@ -0,0 +1,26 @@ +languageId: typescript +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: false + action: {name: clearAndSetSelection} +initialState: + documentContents: foo(hello, world) + selections: + - anchor: {line: 0, character: 10} + active: {line: 0, character: 10} + marks: {} +finalState: + documentContents: foo(hello, ) + selections: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} + thatMark: + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem11.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem11.yml new file mode 100644 index 0000000000..b363279425 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem11.yml @@ -0,0 +1,26 @@ +languageId: typescript +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: false + action: {name: clearAndSetSelection} +initialState: + documentContents: foo(hello, world) + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 13} + marks: {} +finalState: + documentContents: foo() + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem2.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem2.yml new file mode 100644 index 0000000000..9b3800bfec --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem2.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + 111, + "a\"b\"c", + 222, + ] + selections: + - anchor: {line: 2, character: 9} + active: {line: 2, character: 9} + marks: {} +finalState: + documentContents: |- + [ + 111, + , + 222, + ] + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + thatMark: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem3.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem3.yml new file mode 100644 index 0000000000..0114305869 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem3.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + 111, + "a\"b\"c", + 222, + ] + selections: + - anchor: {line: 2, character: 12} + active: {line: 2, character: 12} + marks: {} +finalState: + documentContents: |- + [ + 111, + , + 222, + ] + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + thatMark: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem4.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem4.yml new file mode 100644 index 0000000000..f76256abf8 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem4.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: (aaa) + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem5.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem5.yml new file mode 100644 index 0000000000..73d5d86cb3 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem5.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: aaa aaa, bbb bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: ", bbb bbb" + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem6.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem6.yml new file mode 100644 index 0000000000..cb400b2436 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem6.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: aaa aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem7.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem7.yml new file mode 100644 index 0000000000..8ada6d822b --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem7.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: a, "foo(bar)baz", b + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + marks: {} +finalState: + documentContents: a, "foo()baz", b + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + thatMark: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem8.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem8.yml new file mode 100644 index 0000000000..7371d7c2d8 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem8.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: a, "foo(bar)baz", b + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: a, , b + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItem9.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItem9.yml new file mode 100644 index 0000000000..c6197799f0 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItem9.yml @@ -0,0 +1,26 @@ +languageId: typescript +command: + spokenForm: clear item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: foo(hello, world) + selections: + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + marks: {} +finalState: + documentContents: foo(, world) + 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}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/clearItemDrip.yml b/src/test/suite/fixtures/recorded/itemTextual/clearItemDrip.yml new file mode 100644 index 0000000000..3ae86e27a4 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/clearItemDrip.yml @@ -0,0 +1,30 @@ +languageId: typescript +command: + spokenForm: clear item drip + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + mark: {type: decoratedSymbol, symbolColor: default, character: ','} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: foo(hello, world) + selections: + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + marks: + default.,: + start: {line: 0, character: 9} + end: {line: 0, character: 10} +finalState: + documentContents: foo() + 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: decoratedSymbol, symbolColor: default, character: ','}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/drinkItem.yml b/src/test/suite/fixtures/recorded/itemTextual/drinkItem.yml new file mode 100644 index 0000000000..d1066592b4 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/drinkItem.yml @@ -0,0 +1,37 @@ +languageId: plaintext +command: + spokenForm: drink item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: editNewLineBefore} +initialState: + documentContents: |- + [ + aaa, + bbb, + ccc + ] + selections: + - anchor: {line: 3, character: 7} + active: {line: 3, character: 7} + marks: {} +finalState: + documentContents: |- + [ + aaa, + bbb, + , + ccc + ] + selections: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 4, character: 4} + active: {line: 4, character: 7} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/drinkItem2.yml b/src/test/suite/fixtures/recorded/itemTextual/drinkItem2.yml new file mode 100644 index 0000000000..7377d91886 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/drinkItem2.yml @@ -0,0 +1,37 @@ +languageId: plaintext +command: + spokenForm: drink item + version: 2 + targets: + - type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: editNewLineBefore} +initialState: + documentContents: |- + [ + aaa, + bbb, + ccc, + ] + selections: + - anchor: {line: 3, character: 7} + active: {line: 3, character: 7} + marks: {} +finalState: + documentContents: |- + [ + aaa, + bbb, + , + ccc, + ] + selections: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} + thatMark: + - anchor: {line: 4, character: 4} + active: {line: 4, character: 7} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/takeEveryItem.yml b/src/test/suite/fixtures/recorded/itemTextual/takeEveryItem.yml new file mode 100644 index 0000000000..43559479e1 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/takeEveryItem.yml @@ -0,0 +1,72 @@ +languageId: plaintext +command: + spokenForm: take every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: setSelection} +initialState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + ] + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 10} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 10} + - anchor: {line: 3, character: 10} + active: {line: 3, character: 10} + - anchor: {line: 4, character: 10} + active: {line: 4, character: 10} + marks: {} +finalState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + ] + selections: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 11} + - anchor: {line: 1, character: 13} + active: {line: 1, character: 14} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 11} + - anchor: {line: 2, character: 13} + active: {line: 2, character: 14} + - anchor: {line: 3, character: 10} + active: {line: 3, character: 11} + - anchor: {line: 3, character: 13} + active: {line: 3, character: 14} + - anchor: {line: 4, character: 10} + active: {line: 4, character: 11} + - anchor: {line: 4, character: 13} + active: {line: 4, character: 14} + thatMark: + - anchor: {line: 1, character: 10} + active: {line: 1, character: 11} + - anchor: {line: 1, character: 13} + active: {line: 1, character: 14} + - anchor: {line: 2, character: 10} + active: {line: 2, character: 11} + - anchor: {line: 2, character: 13} + active: {line: 2, character: 14} + - anchor: {line: 3, character: 10} + active: {line: 3, character: 11} + - anchor: {line: 3, character: 13} + active: {line: 3, character: 14} + - anchor: {line: 4, character: 10} + active: {line: 4, character: 11} + - anchor: {line: 4, character: 13} + active: {line: 4, character: 14} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/takeEveryItemAir.yml b/src/test/suite/fixtures/recorded/itemTextual/takeEveryItemAir.yml new file mode 100644 index 0000000000..c55cb78c70 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/takeEveryItemAir.yml @@ -0,0 +1,90 @@ +languageId: plaintext +command: + spokenForm: take every item air + version: 2 + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: a} + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: setSelection} +initialState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.a: + start: {line: 1, character: 4} + end: {line: 1, character: 5} +finalState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} + - anchor: {line: 1, character: 9} + active: {line: 1, character: 15} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 7} + - anchor: {line: 2, character: 9} + active: {line: 2, character: 15} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 7} + - anchor: {line: 3, character: 9} + active: {line: 3, character: 15} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 7} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 15} + - anchor: {line: 5, character: 4} + active: {line: 5, character: 7} + - anchor: {line: 5, character: 9} + active: {line: 5, character: 15} + - anchor: {line: 6, character: 4} + active: {line: 6, character: 7} + - anchor: {line: 6, character: 9} + active: {line: 6, character: 22} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} + - anchor: {line: 1, character: 9} + active: {line: 1, character: 15} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 7} + - anchor: {line: 2, character: 9} + active: {line: 2, character: 15} + - anchor: {line: 3, character: 4} + active: {line: 3, character: 7} + - anchor: {line: 3, character: 9} + active: {line: 3, character: 15} + - anchor: {line: 4, character: 4} + active: {line: 4, character: 7} + - anchor: {line: 4, character: 9} + active: {line: 4, character: 15} + - anchor: {line: 5, character: 4} + active: {line: 5, character: 7} + - anchor: {line: 5, character: 9} + active: {line: 5, character: 15} + - anchor: {line: 6, character: 4} + active: {line: 6, character: 7} + - anchor: {line: 6, character: 9} + active: {line: 6, character: 22} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: a}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/takeItemAir.yml b/src/test/suite/fixtures/recorded/itemTextual/takeItemAir.yml new file mode 100644 index 0000000000..0af92e3b73 --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/takeItemAir.yml @@ -0,0 +1,46 @@ +languageId: plaintext +command: + spokenForm: take item air + version: 2 + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: a} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: setSelection} +initialState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.a: + start: {line: 1, character: 4} + end: {line: 1, character: 5} +finalState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} + thatMark: + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: a}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/itemTextual/takeItemBlueRisk.yml b/src/test/suite/fixtures/recorded/itemTextual/takeItemBlueRisk.yml new file mode 100644 index 0000000000..bfb208408f --- /dev/null +++ b/src/test/suite/fixtures/recorded/itemTextual/takeItemBlueRisk.yml @@ -0,0 +1,46 @@ +languageId: plaintext +command: + spokenForm: take item blue risk + version: 2 + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: blue, character: r} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: setSelection} +initialState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + blue.r: + start: {line: 6, character: 14} + end: {line: 6, character: 17} +finalState: + documentContents: |- + [ + a b, (c, d), + e f, {g, h}, + i j, [k, l], + m n, , + q r, "s, t", + u v, "foo(bar)baz", + ] + selections: + - anchor: {line: 6, character: 14} + active: {line: 6, character: 17} + thatMark: + - anchor: {line: 6, character: 14} + active: {line: 6, character: 17} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: blue, character: r}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/languages/python/clearEveryItem.yml b/src/test/suite/fixtures/recorded/languages/python/clearEveryItem.yml new file mode 100644 index 0000000000..15fa41ed76 --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/python/clearEveryItem.yml @@ -0,0 +1,34 @@ +languageId: python +command: + spokenForm: clear every item + version: 2 + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: from talon import Context, Module, actions + selections: + - anchor: {line: 0, character: 25} + active: {line: 0, character: 25} + marks: {} +finalState: + documentContents: "from talon import , , " + selections: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} + thatMark: + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} + - anchor: {line: 0, character: 20} + active: {line: 0, character: 20} + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} +fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: everyScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/clearItemBrace.yml b/src/test/suite/fixtures/recorded/languages/typescript/clearItemBrace.yml new file mode 100644 index 0000000000..a13d0fc93c --- /dev/null +++ b/src/test/suite/fixtures/recorded/languages/typescript/clearItemBrace.yml @@ -0,0 +1,38 @@ +languageId: typescript +command: + spokenForm: clear item brace + version: 2 + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: '{'} + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true + action: {name: clearAndSetSelection} +initialState: + documentContents: |- + [ + { + foo: "bar", + }, + ] + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.{: + start: {line: 1, character: 2} + end: {line: 1, character: 3} +finalState: + documentContents: |- + [ + , + ] + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + thatMark: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} +fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: '{'}, modifiers: [{type: containingScope, scopeType: {type: collectionItem}}]}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeItemBrace.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeItemBrace.yml deleted file mode 100644 index a562da0bfb..0000000000 --- a/src/test/suite/fixtures/recorded/languages/typescript/takeItemBrace.yml +++ /dev/null @@ -1,31 +0,0 @@ -languageId: typescript -command: - version: 1 - spokenForm: take item brace - action: setSelection - targets: - - type: primitive - modifier: {type: containingScope, scopeType: collectionItem} - mark: {type: decoratedSymbol, symbolColor: default, character: '{'} -initialState: - documentContents: | - - const value = { a: 1, b: 2, c: 3 }; - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} - marks: - default.{: - start: {line: 1, character: 14} - end: {line: 1, character: 15} -finalState: - documentContents: | - - const value = { a: 1, b: 2, c: 3 }; - selections: - - anchor: {line: 1, character: 16} - active: {line: 1, character: 20} - thatMark: - - anchor: {line: 1, character: 16} - active: {line: 1, character: 20} -fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: '{'}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: collectionItem}, insideOutsideType: inside}] diff --git a/src/test/suite/fixtures/recorded/languages/typescript/takeItemComma.yml b/src/test/suite/fixtures/recorded/languages/typescript/takeItemComma.yml index 95ee786767..de76509599 100644 --- a/src/test/suite/fixtures/recorded/languages/typescript/takeItemComma.yml +++ b/src/test/suite/fixtures/recorded/languages/typescript/takeItemComma.yml @@ -23,9 +23,9 @@ finalState: const value = { a: 1, b: 2, c: 3 }; selections: - - anchor: {line: 1, character: 22} + - anchor: {line: 1, character: 16} active: {line: 1, character: 26} thatMark: - - anchor: {line: 1, character: 22} + - anchor: {line: 1, character: 16} active: {line: 1, character: 26} fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: ','}, selectionType: token, position: contents, modifier: {type: containingScope, scopeType: collectionItem}, insideOutsideType: inside}] diff --git a/src/typings/targetDescriptor.types.ts b/src/typings/targetDescriptor.types.ts index cc95e9ab3f..7c6919b27a 100644 --- a/src/typings/targetDescriptor.types.ts +++ b/src/typings/targetDescriptor.types.ts @@ -60,7 +60,10 @@ export type SimpleSurroundingPairName = | "parentheses" | "singleQuotes" | "squareBrackets"; -export type ComplexSurroundingPairName = "string" | "any"; +export type ComplexSurroundingPairName = + | "string" + | "any" + | "collectionBoundary"; export type SurroundingPairName = | SimpleSurroundingPairName | ComplexSurroundingPairName; diff --git a/src/util/nodeSelectors.ts b/src/util/nodeSelectors.ts index ccc0845f11..5088c17fb9 100644 --- a/src/util/nodeSelectors.ts +++ b/src/util/nodeSelectors.ts @@ -1,11 +1,11 @@ -import { SyntaxNode, Point } from "web-tree-sitter"; +import { identity, maxBy } from "lodash"; import { Position, Range, Selection, TextEditor } from "vscode"; +import { Point, SyntaxNode } from "web-tree-sitter"; import { - SelectionWithContext, - SelectionExtractor, NodeFinder, + SelectionExtractor, + SelectionWithContext, } from "../typings/Types"; -import { identity } from "lodash"; export function makeRangeFromPositions( startPosition: Point, @@ -328,47 +328,41 @@ export function delimitedSelector( getEndNode: (node: SyntaxNode) => SyntaxNode = identity ): SelectionExtractor { return (editor: TextEditor, node: SyntaxNode) => { - let containingListDelimiter: string | undefined; let leadingDelimiterRange: Range | undefined; let trailingDelimiterRange: Range | undefined; const startNode = getStartNode(node); const endNode = getEndNode(node); - const nextNonDelimiterNode = getNextNonDelimiterNode( - endNode, - isDelimiterNode - ); const previousNonDelimiterNode = getPreviousNonDelimiterNode( startNode, isDelimiterNode ); - - if (nextNonDelimiterNode != null) { - trailingDelimiterRange = makeRangeFromPositions( - endNode.endPosition, - nextNonDelimiterNode.startPosition - ); - - containingListDelimiter = editor.document.getText(trailingDelimiterRange); - } + const nextNonDelimiterNode = getNextNonDelimiterNode( + endNode, + isDelimiterNode + ); if (previousNonDelimiterNode != null) { leadingDelimiterRange = makeRangeFromPositions( previousNonDelimiterNode.endPosition, startNode.startPosition ); - - if (containingListDelimiter == null) { - containingListDelimiter = editor.document.getText( - leadingDelimiterRange - ); - } } - if (containingListDelimiter == null) { - containingListDelimiter = defaultDelimiter; + if (nextNonDelimiterNode != null) { + trailingDelimiterRange = makeRangeFromPositions( + endNode.endPosition, + nextNonDelimiterNode.startPosition + ); } + const containingListDelimiter = getInsertionDelimiter( + editor, + leadingDelimiterRange, + trailingDelimiterRange, + defaultDelimiter + ); + return { selection: new Selection( new Position( @@ -385,3 +379,22 @@ export function delimitedSelector( }; }; } + +export function getInsertionDelimiter( + editor: TextEditor, + leadingDelimiterRange: Range | undefined, + trailingDelimiterRange: Range | undefined, + defaultDelimiterInsertion: string +) { + const { getText } = editor.document; + const delimiters = [ + trailingDelimiterRange != null + ? getText(trailingDelimiterRange) + : defaultDelimiterInsertion, + leadingDelimiterRange != null + ? getText(leadingDelimiterRange) + : defaultDelimiterInsertion, + ]; + + return maxBy(delimiters, "length"); +}