diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts index a5077ffbde..92177ace8f 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/queryPredicateOperators.ts @@ -214,6 +214,37 @@ class InsertionDelimiter extends QueryPredicateOperator { } } +/** + * A predicate operator that sets the insertion delimiter of {@link nodeInfo} to + * either {@link insertionDelimiterConsequence} or + * {@link insertionDelimiterAlternative} depending on whether + * {@link conditionNodeInfo} is single or multiline, respectively. For example, + * + * ```scm + * (#single-or-multi-line-delimiter! @foo @bar ", " ",\n") + * ``` + * + * will set the insertion delimiter of the `@foo` capture to `", "` if the + * `@bar` capture is a single line and `",\n"` otherwise. + */ +class SingleOrMultilineDelimiter extends QueryPredicateOperator { + name = "single-or-multi-line-delimiter!" as const; + schema = z.tuple([q.node, q.node, q.string, q.string]); + + run( + nodeInfo: MutableQueryCapture, + conditionNodeInfo: MutableQueryCapture, + insertionDelimiterConsequence: string, + insertionDelimiterAlternative: string, + ) { + nodeInfo.insertionDelimiter = conditionNodeInfo.range.isSingleLine + ? insertionDelimiterConsequence + : insertionDelimiterAlternative; + + return true; + } +} + export const queryPredicateOperators = [ new Log(), new NotType(), @@ -224,5 +255,6 @@ export const queryPredicateOperators = [ new ShrinkToMatch(), new AllowMultiple(), new InsertionDelimiter(), + new SingleOrMultilineDelimiter(), new HasMultipleChildrenOfType(), ]; diff --git a/packages/cursorless-engine/src/languages/getNodeMatcher.ts b/packages/cursorless-engine/src/languages/getNodeMatcher.ts index c74cfd3099..39aee32b25 100644 --- a/packages/cursorless-engine/src/languages/getNodeMatcher.ts +++ b/packages/cursorless-engine/src/languages/getNodeMatcher.ts @@ -22,7 +22,6 @@ import { patternMatchers as ruby } from "./ruby"; import rust from "./rust"; import scala from "./scala"; import { patternMatchers as scss } from "./scss"; -import { patternMatchers as typescript } from "./typescript"; export function getNodeMatcher( languageId: string, @@ -59,8 +58,6 @@ export const languageMatchers: Record< clojure, go, java, - javascript: typescript, - javascriptreact: typescript, latex, markdown, php, @@ -69,8 +66,6 @@ export const languageMatchers: Record< scala, scss, rust, - typescript, - typescriptreact: typescript, }; function matcherIncludeSiblings(matcher: NodeMatcher): NodeMatcher { diff --git a/packages/cursorless-engine/src/languages/markdown.ts b/packages/cursorless-engine/src/languages/markdown.ts index e08c481482..9ccd16ddc1 100644 --- a/packages/cursorless-engine/src/languages/markdown.ts +++ b/packages/cursorless-engine/src/languages/markdown.ts @@ -1,17 +1,9 @@ -import { Range, SimpleScopeTypeType, TextEditor } from "@cursorless/common"; +import { SimpleScopeTypeType, TextEditor } from "@cursorless/common"; import type { SyntaxNode } from "web-tree-sitter"; -import { - NodeFinder, - NodeMatcherAlternative, - SelectionWithContext, -} from "../typings/Types"; -import { getMatchesInRange } from "../util/getMatchesInRange"; +import { NodeFinder, NodeMatcherAlternative } from "../typings/Types"; import { leadingSiblingNodeFinder, patternFinder } from "../util/nodeFinders"; import { createPatternMatchers, matcher } from "../util/nodeMatchers"; -import { - extendUntilNextMatchingSiblingOrLast, - selectWithLeadingDelimiter, -} from "../util/nodeSelectors"; +import { extendUntilNextMatchingSiblingOrLast } from "../util/nodeSelectors"; import { shrinkRangeToFitContent } from "../util/selectionUtils"; const HEADING_MARKER_TYPES = [ @@ -69,48 +61,9 @@ function sectionMatcher(...patterns: string[]) { return matcher(leadingSiblingNodeFinder(finder), sectionExtractor); } -const itemLeadingDelimiterExtractor = selectWithLeadingDelimiter( - "list_marker_parenthesis", - "list_marker_dot", - "list_marker_star", - "list_marker_minus", - "list_marker_plus", -); - -function excludeTrailingNewline(editor: TextEditor, range: Range) { - const matches = getMatchesInRange(/\r?\n\s*$/g, editor, range); - - if (matches.length > 0) { - return new Range(range.start, matches[0].start); - } - - return range; -} - -function itemExtractor( - editor: TextEditor, - node: SyntaxNode, -): SelectionWithContext { - const { selection } = itemLeadingDelimiterExtractor(editor, node); - const line = editor.document.lineAt(selection.start); - const leadingRange = new Range(line.range.start, selection.start); - const indent = editor.document.getText(leadingRange); - - return { - context: { - containingListDelimiter: `\n${indent}`, - leadingDelimiterRange: leadingRange, - }, - selection: excludeTrailingNewline(editor, selection).toSelection( - selection.isReversed, - ), - }; -} - const nodeMatchers: Partial< Record > = { - collectionItem: matcher(patternFinder("list_item.paragraph!"), itemExtractor), section: sectionMatcher("atx_heading"), sectionLevelOne: sectionMatcher("atx_heading.atx_h1_marker"), sectionLevelTwo: sectionMatcher("atx_heading.atx_h2_marker"), diff --git a/packages/cursorless-engine/src/languages/typescript.ts b/packages/cursorless-engine/src/languages/typescript.ts deleted file mode 100644 index 319e7fb0b4..0000000000 --- a/packages/cursorless-engine/src/languages/typescript.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SimpleScopeTypeType } from "@cursorless/common"; -import { NodeMatcherAlternative } from "../typings/Types"; -import { argumentMatcher, createPatternMatchers } from "../util/nodeMatchers"; - -const nodeMatchers: Partial< - Record -> = { - argumentOrParameter: argumentMatcher("formal_parameters", "arguments"), -}; - -export const patternMatchers = createPatternMatchers(nodeMatchers); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index 74785bb372..b94bf21e50 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -73,13 +73,20 @@ export class EveryScopeStage implements ModifierStage { if (scopes == null) { // If target had no explicit range, or was contained by a single target // instance, expand to iteration scope before overlapping - scopes = this.getDefaultIterationRange( - scopeHandler, - this.scopeHandlerFactory, - target, - ).flatMap((iterationRange) => - getScopesOverlappingRange(scopeHandler, editor, iterationRange), - ); + try { + scopes = this.getDefaultIterationRange( + scopeHandler, + this.scopeHandlerFactory, + target, + ).flatMap((iterationRange) => + getScopesOverlappingRange(scopeHandler, editor, iterationRange), + ); + } catch (error) { + if (!(error instanceof NoContainingScopeError)) { + throw error; + } + scopes = []; + } } if (scopes.length === 0) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ItemStage/ItemStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/ItemStage/ItemStage.ts index f2ef697e35..7f595356e1 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ItemStage/ItemStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ItemStage/ItemStage.ts @@ -8,7 +8,6 @@ import { } from "@cursorless/common"; import { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; import { Target } from "../../../typings/target.types"; -import { getInsertionDelimiter } from "../../../util/nodeSelectors"; import { getRangeLength } from "../../../util/rangeUtils"; import { ModifierStage } from "../../PipelineStages.types"; import { ScopeTypeTarget } from "../../targets"; @@ -109,18 +108,16 @@ export class ItemStage implements ModifierStage { itemInfo: ItemInfo, removalRange?: Range, ) { - const delimiter = getInsertionDelimiter( - target.editor, + const insertionDelimiter = getInsertionDelimiter( itemInfo.leadingDelimiterRange, itemInfo.trailingDelimiterRange, - ", ", ); return new ScopeTypeTarget({ scopeTypeType: this.modifier.scopeType.type as SimpleScopeTypeType, editor: target.editor, isReversed: target.isReversed, contentRange: itemInfo.contentRange, - delimiter, + insertionDelimiter, leadingDelimiterRange: itemInfo.leadingDelimiterRange, trailingDelimiterRange: itemInfo.trailingDelimiterRange, removalRange, @@ -128,6 +125,17 @@ export class ItemStage implements ModifierStage { } } +function getInsertionDelimiter( + leadingDelimiterRange?: Range, + trailingDelimiterRange?: Range, +): string { + return (leadingDelimiterRange != null && + !leadingDelimiterRange.isSingleLine) || + (trailingDelimiterRange != null && !trailingDelimiterRange.isSingleLine) + ? ",\n" + : ", "; +} + /** Filter item infos by content range and domain intersection */ function filterItemInfos(target: Target, itemInfos: ItemInfo[]): ItemInfo[] { return itemInfos.filter( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts index d05940133f..3aa95dac22 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts @@ -70,6 +70,17 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { true, ); + const rawPrefixRange = getRelatedRange( + match, + scopeTypeType, + "prefix", + true, + ); + const prefixRange = + rawPrefixRange != null + ? new Range(rawPrefixRange.start, contentRange.start) + : undefined; + return { editor, domain, @@ -80,11 +91,12 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { editor, isReversed, contentRange, + prefixRange, removalRange, leadingDelimiterRange, trailingDelimiterRange, interiorRange, - delimiter: insertionDelimiter, + insertionDelimiter, }), ], }; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts index baed206ed3..b099e10dca 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts @@ -82,7 +82,7 @@ export class LegacyContainingSyntaxScopeStage implements ModifierStage { contentRange: contentSelection, removalRange: removalRange, interiorRange: interiorRange, - delimiter: containingListDelimiter, + insertionDelimiter: containingListDelimiter, leadingDelimiterRange, trailingDelimiterRange, }); diff --git a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts index aabb637ae4..a7087d4e46 100644 --- a/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts +++ b/packages/cursorless-engine/src/processTargets/targets/DestinationImpl.ts @@ -10,12 +10,14 @@ import { EditNewActionType, Target, } from "../../typings/target.types"; +import { union } from "../../util/rangeUtils"; export class DestinationImpl implements Destination { public readonly contentRange: Range; private readonly isLineDelimiter: boolean; private readonly isBefore: boolean; private readonly indentationString: string; + private readonly insertionPrefix?: string; constructor( public readonly target: Target, @@ -24,12 +26,15 @@ export class DestinationImpl implements Destination { ) { this.contentRange = getContentRange(target.contentRange, insertionMode); this.isBefore = insertionMode === "before"; - // It's only considered a line if the delimiter is only new line symbols - this.isLineDelimiter = /^(\n)+$/.test(target.insertionDelimiter); + this.isLineDelimiter = target.insertionDelimiter.includes("\n"); this.indentationString = indentationString ?? this.isLineDelimiter ? getIndentationString(target.editor, target.contentRange) : ""; + this.insertionPrefix = + target.prefixRange != null + ? target.editor.document.getText(target.prefixRange) + : undefined; } get contentSelection(): Selection { @@ -65,9 +70,10 @@ export class DestinationImpl implements Destination { getEditNewActionType(): EditNewActionType { if ( - this.insertionDelimiter === "\n" && this.insertionMode === "after" && - this.target.contentRange.isSingleLine + this.target.contentRange.isSingleLine && + this.insertionDelimiter === "\n" && + this.insertionPrefix == null ) { // If the target that we're wrapping is not a single line, then we // want to compute indentation based on the entire target. Otherwise, @@ -110,30 +116,31 @@ export class DestinationImpl implements Destination { private getEditRange() { const position = (() => { - const contentPosition = this.isBefore - ? this.contentRange.start - : this.contentRange.end; + const insertionPosition = this.isBefore + ? union(this.target.contentRange, this.target.prefixRange).start + : this.target.contentRange.end; if (this.isLineDelimiter) { - const line = this.editor.document.lineAt(contentPosition); + const line = this.editor.document.lineAt(insertionPosition); const nonWhitespaceCharacterIndex = this.isBefore ? line.firstNonWhitespaceCharacterIndex : line.lastNonWhitespaceCharacterIndex; - // Use the full line to include indentation - if (contentPosition.character === nonWhitespaceCharacterIndex) { + // Use the full line with included indentation and trailing whitespaces + if (insertionPosition.character === nonWhitespaceCharacterIndex) { return this.isBefore ? line.range.start : line.range.end; } } - return contentPosition; + return insertionPosition; })(); return new Range(position, position); } private getEditText(text: string) { - const insertionText = this.indentationString + text; + const insertionText = + this.indentationString + (this.insertionPrefix ?? "") + text; return this.isBefore ? insertionText + this.insertionDelimiter @@ -141,12 +148,14 @@ export class DestinationImpl implements Destination { } private updateRange(range: Range, text: string) { - const baseStartOffset = this.editor.document.offsetAt(range.start); + const baseStartOffset = + this.editor.document.offsetAt(range.start) + + this.indentationString.length + + (this.insertionPrefix?.length ?? 0); + const startIndex = this.isBefore - ? baseStartOffset + this.indentationString.length - : baseStartOffset + - this.getLengthOfInsertionDelimiter() + - this.indentationString.length; + ? baseStartOffset + : baseStartOffset + this.getLengthOfInsertionDelimiter(); const endIndex = startIndex + text.length; @@ -160,11 +169,10 @@ export class DestinationImpl implements Destination { // Went inserting a new line with eol `CRLF` each `\n` will be converted to // `\r\n` and therefore the length is doubled. if (this.editor.document.eol === "CRLF") { - // This function is only called when inserting after a range. Therefore we - // only care about leading new lines in the insertion delimiter. - const match = this.insertionDelimiter.match(/^\n+/); - const nlCount = match?.[0].length ?? 0; - return this.insertionDelimiter.length + nlCount; + const matches = this.insertionDelimiter.match(/\n/g); + if (matches != null) { + return this.insertionDelimiter.length + matches.length; + } } return this.insertionDelimiter.length; } diff --git a/packages/cursorless-engine/src/processTargets/targets/ScopeTypeTarget.ts b/packages/cursorless-engine/src/processTargets/targets/ScopeTypeTarget.ts index 02bf7814f4..4e31218c04 100644 --- a/packages/cursorless-engine/src/processTargets/targets/ScopeTypeTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/ScopeTypeTarget.ts @@ -16,7 +16,8 @@ import { export interface ScopeTypeTargetParameters extends CommonTargetParameters { readonly scopeTypeType: SimpleScopeTypeType; - readonly delimiter?: string; + readonly insertionDelimiter?: string; + readonly prefixRange?: Range; readonly removalRange?: Range; readonly interiorRange?: Range; readonly leadingDelimiterRange?: Range; @@ -31,7 +32,8 @@ export class ScopeTypeTarget extends BaseTarget { private leadingDelimiterRange_?: Range; private trailingDelimiterRange_?: Range; private hasDelimiterRange_: boolean; - insertionDelimiter: string; + public readonly prefixRange?: Range; + readonly insertionDelimiter: string; constructor(parameters: ScopeTypeTargetParameters) { super(parameters); @@ -40,8 +42,10 @@ export class ScopeTypeTarget extends BaseTarget { this.interiorRange_ = parameters.interiorRange; this.leadingDelimiterRange_ = parameters.leadingDelimiterRange; this.trailingDelimiterRange_ = parameters.trailingDelimiterRange; + this.prefixRange = parameters.prefixRange; this.insertionDelimiter = - parameters.delimiter ?? getDelimiter(parameters.scopeTypeType); + parameters.insertionDelimiter ?? + getInsertionDelimiter(parameters.scopeTypeType); this.hasDelimiterRange_ = !!this.leadingDelimiterRange_ || !!this.trailingDelimiterRange_; } @@ -126,18 +130,18 @@ export class ScopeTypeTarget extends BaseTarget { protected getCloneParameters() { return { ...this.state, - delimiter: this.insertionDelimiter, + insertionDelimiter: this.insertionDelimiter, + prefixRange: this.prefixRange, removalRange: undefined, interiorRange: undefined, scopeTypeType: this.scopeTypeType_, - contentRemovalRange: this.removalRange_, leadingDelimiterRange: this.leadingDelimiterRange_, trailingDelimiterRange: this.trailingDelimiterRange_, }; } } -function getDelimiter(scopeType: SimpleScopeTypeType): string { +function getInsertionDelimiter(scopeType: SimpleScopeTypeType): string { switch (scopeType) { case "class": case "namedFunction": diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts index 6132a78512..94626a50da 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/DelimitedSequenceInsertionRemovalBehavior.ts @@ -1,5 +1,6 @@ import { Range } from "@cursorless/common"; import { Target } from "../../../../typings/target.types"; +import { union } from "../../../../util/rangeUtils"; /** * Constructs a removal range for the given target that includes either the @@ -8,7 +9,7 @@ import { Target } from "../../../../typings/target.types"; * @returns The removal range for the given target */ export function getDelimitedSequenceRemovalRange(target: Target): Range { - const { contentRange } = target; + const contentRange = union(target.contentRange, target.prefixRange); const delimiterTarget = target.getTrailingDelimiterTarget() ?? target.getLeadingDelimiterTarget(); diff --git a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts index 160cab6351..ee75898054 100644 --- a/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts +++ b/packages/cursorless-engine/src/processTargets/targets/util/insertionRemovalBehaviors/TokenInsertionRemovalBehavior.ts @@ -1,6 +1,6 @@ import { Range, TextEditor } from "@cursorless/common"; import type { Target } from "../../../../typings/target.types"; -import { expandToFullLine } from "../../../../util/rangeUtils"; +import { expandToFullLine, union } from "../../../../util/rangeUtils"; import { PlainTarget } from "../../PlainTarget"; const leadingDelimiters = ['"', "'", "(", "[", "{", "<"]; @@ -10,7 +10,7 @@ export function getTokenLeadingDelimiterTarget( target: Target, ): Target | undefined { const { editor } = target; - const { start } = target.contentRange; + const { start } = union(target.contentRange, target.prefixRange); const startLine = editor.document.lineAt(start); const leadingText = startLine.text.slice(0, start.character); @@ -80,7 +80,8 @@ export function getTokenTrailingDelimiterTarget( * @returns The removal range for the given target */ export function getTokenRemovalRange(target: Target): Range { - const { editor, contentRange } = target; + const { editor } = target; + const contentRange = union(target.contentRange, target.prefixRange); const { start, end } = contentRange; const leadingWhitespaceRange = diff --git a/packages/cursorless-engine/src/typings/target.types.ts b/packages/cursorless-engine/src/typings/target.types.ts index 7f4042cb36..e817ea45db 100644 --- a/packages/cursorless-engine/src/typings/target.types.ts +++ b/packages/cursorless-engine/src/typings/target.types.ts @@ -42,6 +42,9 @@ export interface Target { /** If this selection has a delimiter use it for inserting before or after the target. For example, new line for a line or paragraph and comma for a list or argument */ readonly insertionDelimiter: string; + /** Optional prefix. For example, dash or asterisk for a markdown item */ + readonly prefixRange?: Range; + /** If true this target should be treated as a line */ readonly isLine: boolean; diff --git a/packages/cursorless-engine/src/util/rangeUtils.ts b/packages/cursorless-engine/src/util/rangeUtils.ts index 89bf2aebd3..79dddd435d 100644 --- a/packages/cursorless-engine/src/util/rangeUtils.ts +++ b/packages/cursorless-engine/src/util/rangeUtils.ts @@ -53,3 +53,15 @@ export function strictlyContains( : [rangeOrPosition.start, rangeOrPosition.end]; return range1.start.isBefore(start) && range1.end.isAfter(end); } + +/** + * Make union between range and additional optional ranges + */ +export function union(range: Range, ...unionWith: (Range | undefined)[]) { + for (const r of unionWith) { + if (r != null) { + range = range.union(r); + } + } + return range; +} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeLeadingItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeLeadingItem.yml new file mode 100644 index 0000000000..fd0d38aece --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeLeadingItem.yml @@ -0,0 +1,31 @@ +languageId: markdown +command: + version: 6 + spokenForm: change leading item + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: leading} + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - values + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + - values + - 0- 1 + - 2 + selections: + - anchor: {line: 1, character: 7} + active: {line: 1, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeTrailingItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeTrailingItem.yml new file mode 100644 index 0000000000..46dadadaf7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/changeTrailingItem.yml @@ -0,0 +1,31 @@ +languageId: markdown +command: + version: 6 + spokenForm: change trailing item + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: trailing} + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - values + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + - values + - 0 + - 1- 2 + selections: + - anchor: {line: 2, character: 7} + active: {line: 2, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem.yml index 2c886b2f20..fde3f9ecf9 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem.yml @@ -18,7 +18,6 @@ initialState: finalState: documentContents: |- - - whatever now selections: - anchor: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem2.yml index 69a6c36df6..f1e0cdb73c 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem2.yml @@ -21,8 +21,7 @@ initialState: finalState: documentContents: |- - aaa - - ddd selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem3.yml new file mode 100644 index 0000000000..f6e10d8a8d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem3.yml @@ -0,0 +1,30 @@ +languageId: markdown +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - values + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + - values + - 0 + - 2 + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem4.yml new file mode 100644 index 0000000000..9178465ace --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem4.yml @@ -0,0 +1,28 @@ +languageId: markdown +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + - values + - 0 + - 2 + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + - values + - 0 + selections: + - anchor: {line: 1, character: 7} + active: {line: 1, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem5.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem5.yml new file mode 100644 index 0000000000..28ea83d69d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/chuckItem5.yml @@ -0,0 +1,26 @@ +languageId: markdown +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + - values + - 0 + selections: + - anchor: {line: 1, character: 7} + active: {line: 1, character: 7} + marks: {} +finalState: + documentContents: | + - values + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneItem.yml new file mode 100644 index 0000000000..2d9a5e3b46 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneItem.yml @@ -0,0 +1,30 @@ +languageId: markdown +command: + version: 6 + spokenForm: clone item + action: + name: insertCopyAfter + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - aaa + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + - aaa + - bbb + - bbb + - ccc + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneUpItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneUpItem.yml new file mode 100644 index 0000000000..ea8bb53e5b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/cloneUpItem.yml @@ -0,0 +1,30 @@ +languageId: markdown +command: + version: 6 + spokenForm: clone up item + action: + name: insertCopyBefore + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - aaa + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + - aaa + - bbb + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/drinkItem2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/drinkItem2.yml new file mode 100644 index 0000000000..6eeab87bc7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/drinkItem2.yml @@ -0,0 +1,30 @@ +languageId: markdown +command: + version: 6 + spokenForm: drink item + action: + name: editNewLineBefore + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - aaa + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + - aaa + - + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/pourItem3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/pourItem3.yml new file mode 100644 index 0000000000..2cb79a3c7b --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/markdown/pourItem3.yml @@ -0,0 +1,30 @@ +languageId: markdown +command: + version: 6 + spokenForm: pour item + action: + name: editNewLineAfter + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + - aaa + - bbb + - ccc + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + - aaa + - bbb + - + - ccc + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg.yml new file mode 100644 index 0000000000..9303457e26 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg.yml @@ -0,0 +1,35 @@ +languageId: typescript +command: + version: 6 + spokenForm: change every arg + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: argumentOrParameter} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + function funk( + foo: number, // Comment1 + // Comment2 + bar?: string + ) {} + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + function funk( + , // Comment1 + // Comment2 + + ) {} + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + - anchor: {line: 3, character: 2} + active: {line: 3, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg2.yml new file mode 100644 index 0000000000..ff58404877 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/typescript/changeEveryArg2.yml @@ -0,0 +1,35 @@ +languageId: typescript +command: + version: 6 + spokenForm: change every arg + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: argumentOrParameter} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + funk( + "foo", // Comment1 + // Comment2 + 2 + ); + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: |- + funk( + , // Comment1 + // Comment2 + + ); + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + - anchor: {line: 3, character: 2} + active: {line: 3, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeEveryItem3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeEveryItem3.yml index 82bcfcb031..ea006f3821 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeEveryItem3.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeEveryItem3.yml @@ -22,10 +22,10 @@ initialState: finalState: documentContents: |- foo: - - + - + - selections: - - anchor: {line: 1, character: 2} - active: {line: 1, character: 2} - - anchor: {line: 2, character: 2} - active: {line: 2, character: 2} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem2.yml index 42ae1cc305..bdd3b9f678 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem2.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem2.yml @@ -22,8 +22,8 @@ initialState: finalState: documentContents: |- foo: - + - - 1 selections: - - anchor: {line: 1, character: 2} - active: {line: 1, character: 2} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem4.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem4.yml index e0df6c2ea8..c44d3c1792 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem4.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeItem4.yml @@ -18,8 +18,8 @@ initialState: active: {line: 0, character: 0} marks: {} finalState: - documentContents: |+ - + documentContents: | + - selections: - - anchor: {line: 0, character: 0} - active: {line: 0, character: 0} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeLeadingItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeLeadingItem.yml new file mode 100644 index 0000000000..7e7ac03f06 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeLeadingItem.yml @@ -0,0 +1,31 @@ +languageId: yaml +command: + version: 6 + spokenForm: change leading item + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: leading} + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + foo: + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} + marks: {} +finalState: + documentContents: |- + foo: + - 0- 1 + - 2 + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeTrailingItem.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeTrailingItem.yml new file mode 100644 index 0000000000..65e6593d24 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/changeTrailingItem.yml @@ -0,0 +1,31 @@ +languageId: yaml +command: + version: 6 + spokenForm: change trailing item + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: trailing} + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + foo: + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} + marks: {} +finalState: + documentContents: |- + foo: + - 0 + - 1- 2 + selections: + - anchor: {line: 2, character: 5} + active: {line: 2, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem10.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem10.yml new file mode 100644 index 0000000000..2320833dd4 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem10.yml @@ -0,0 +1,28 @@ +languageId: yaml +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + foo: + - 0 + - 2 + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} + marks: {} +finalState: + documentContents: |- + foo: + - 0 + selections: + - anchor: {line: 1, character: 5} + active: {line: 1, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem11.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem11.yml new file mode 100644 index 0000000000..213243ad42 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem11.yml @@ -0,0 +1,26 @@ +languageId: yaml +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + foo: + - 0 + selections: + - anchor: {line: 1, character: 2} + active: {line: 1, character: 2} + marks: {} +finalState: + documentContents: | + foo: + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem9.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem9.yml new file mode 100644 index 0000000000..f54ade268d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/languages/yaml/chuckItem9.yml @@ -0,0 +1,30 @@ +languageId: yaml +command: + version: 6 + spokenForm: chuck item + action: + name: remove + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: collectionItem} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + foo: + - 0 + - 1 + - 2 + selections: + - anchor: {line: 2, character: 3} + active: {line: 2, character: 3} + marks: {} +finalState: + documentContents: |- + foo: + - 0 + - 2 + selections: + - anchor: {line: 2, character: 2} + active: {line: 2, character: 2} diff --git a/queries/javascript.core.scm b/queries/javascript.core.scm index a09bf98818..1e81e248ad 100644 --- a/queries/javascript.core.scm +++ b/queries/javascript.core.scm @@ -680,3 +680,36 @@ ] @statement (#not-parent-type? @statement export_statement) ) + +;;!! foo(name: string) {} +;;! ^^^^^^^^^^^^ +( + (formal_parameters + (_)? @_.leading.start.endOf + . + (_) @argumentOrParameter @_.leading.end.startOf @_.trailing.start.endOf + . + (_)? @_.trailing.end.startOf + ) @dummy + (#not-type? @argumentOrParameter "comment") + (#single-or-multi-line-delimiter! @argumentOrParameter @dummy ", " ",\n") +) + +;;!! foo("bar") +;;! ^^^^^ +( + (arguments + (_)? @_.leading.start.endOf + . + (_) @argumentOrParameter @_.leading.end.startOf @_.trailing.start.endOf + . + (_)? @_.trailing.end.startOf + ) @dummy + (#not-type? @argumentOrParameter "comment") + (#single-or-multi-line-delimiter! @argumentOrParameter @dummy ", " ",\n") +) + +[ + (formal_parameters) + (arguments) +] @argumentOrParameter.iteration diff --git a/queries/markdown.scm b/queries/markdown.scm index 42ae6b1651..a1974f11f6 100644 --- a/queries/markdown.scm +++ b/queries/markdown.scm @@ -17,3 +17,25 @@ ) @_.removal (#shrink-to-match! @name "^\\s*(?.*)$") ) @_.domain + +;;!! - 0 +;;! ^ +;;! --- +(list + (list_item + (paragraph + (inline) @_.leading.start.endOf + ) + )? + . + (list_item + (_) @_.prefix + (paragraph + (inline) @collectionItem @_.trailing.start.endOf + ) + ) @_.domain @_.leading.end.startOf + . + (list_item)? @_.trailing.end.startOf + (#trim-end! @_.domain) + (#insertion-delimiter! @collectionItem "\n") +) diff --git a/queries/yaml.scm b/queries/yaml.scm index 10319b7137..899f208507 100644 --- a/queries/yaml.scm +++ b/queries/yaml.scm @@ -16,15 +16,20 @@ ) @map ;;!! - 0 -;;! ^^^ +;;! ^ +;;! --- ( (block_sequence (block_sequence_item)? @collectionItem.leading.start.endOf . - (block_sequence_item) @collectionItem @collectionItem.leading.end.startOf @collectionItem.trailing.end.endOf + (block_sequence_item + "-" @collectionItem.leading.end.startOf @collectionItem.prefix + (_) @collectionItem @collectionItem.trailing.end.endOf + ) @collectionItem.domain . (block_sequence_item)? @collectionItem.trailing.end.startOf (#trim-end! @collectionItem) + (#insertion-delimiter! @collectionItem "\n") ) @list (#trim-end! @list) )