From f735b580811d8a16432f9cbfbe05de229199916c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 30 Jan 2025 16:04:04 +0100 Subject: [PATCH 1/4] Change implementation of default bounded line for head/tail modifiers --- .../recorded/headTail/changeHead3.yml | 26 +++++ .../command/PartialTargetDescriptor.types.ts | 2 - .../processTargets/modifiers/HeadTailStage.ts | 105 +++++++++++++----- .../scopeHandlers/BoundedScopeHandler.ts | 1 + .../SurroundingPairInteriorScopeHandler.ts | 4 - 5 files changed, 105 insertions(+), 33 deletions(-) create mode 100644 data/fixtures/recorded/headTail/changeHead3.yml diff --git a/data/fixtures/recorded/headTail/changeHead3.yml b/data/fixtures/recorded/headTail/changeHead3.yml new file mode 100644 index 0000000000..609d556db4 --- /dev/null +++ b/data/fixtures/recorded/headTail/changeHead3.yml @@ -0,0 +1,26 @@ +languageId: plaintext +command: + version: 7 + spokenForm: change head + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - {type: extendThroughStartOf} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + (aaa + ) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: |- + ( + ) + selections: + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index 97de296c5d..af7b98cadb 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -253,8 +253,6 @@ export interface SurroundingPairScopeType { export interface SurroundingPairInteriorScopeType { type: "surroundingPairInterior"; delimiter: SurroundingPairName; - // If true don't yield multiline pairs - requireSingleLine?: boolean; } export interface OneOfScopeType { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts index 71af22afa9..de5adc5dea 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts @@ -1,4 +1,9 @@ -import type { HeadModifier, Modifier, TailModifier } from "@cursorless/common"; +import { + NoContainingScopeError, + type HeadModifier, + type ScopeType, + type TailModifier, +} from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; @@ -6,7 +11,7 @@ import { getModifierStagesFromTargetModifiers, processModifierStages, } from "../TargetPipelineRunner"; -import { HeadTailTarget } from "../targets"; +import { HeadTailTarget, PlainTarget } from "../targets"; export class HeadTailStage implements ModifierStage { constructor( @@ -15,34 +20,11 @@ export class HeadTailStage implements ModifierStage { ) {} run(target: Target): Target[] { - const modifiers: Modifier[] = this.modifier.modifiers ?? [ - { - type: "containingScope", - scopeType: { - type: "oneOf", - scopeTypes: [ - { - type: "line", - }, - { - type: "surroundingPairInterior", - delimiter: "any", - requireSingleLine: true, - }, - ], - }, - }, - ]; - - const modifierStages = getModifierStagesFromTargetModifiers( - this.modifierStageFactory, - modifiers, - ); + const modifierStages = this.getModifierStages(); const modifiedTargets = processModifierStages(modifierStages, [target]); + const isHead = this.modifier.type === "extendThroughStartOf"; return modifiedTargets.map((modifiedTarget) => { - const isHead = this.modifier.type === "extendThroughStartOf"; - return new HeadTailTarget({ editor: target.editor, isReversed: isHead, @@ -52,4 +34,73 @@ export class HeadTailStage implements ModifierStage { }); }); } + + private getModifierStages(): ModifierStage[] { + if (this.modifier.modifiers != null) { + return getModifierStagesFromTargetModifiers( + this.modifierStageFactory, + this.modifier.modifiers, + ); + } + + return [new BoundedLineStage(this.modifierStageFactory, this.modifier)]; + } +} + +class BoundedLineStage implements ModifierStage { + constructor( + private modifierStageFactory: ModifierStageFactory, + private modifier: HeadModifier | TailModifier, + ) {} + + run(target: Target): Target[] { + const line = this.getContainingLine(target); + const pairInterior = this.getContainingPairInterior(target); + + if (pairInterior == null) { + return [line]; + } + + const intersection = line.contentRange.intersection( + pairInterior.contentRange, + ); + + if (intersection == null || intersection.isEmpty) { + return [line]; + } + + return [ + new PlainTarget({ + editor: target.editor, + isReversed: target.isReversed, + contentRange: intersection, + }), + ]; + } + + private getContainingPairInterior(target: Target): Target | undefined { + try { + return this.getContaining(target, { + type: "surroundingPairInterior", + delimiter: "any", + })[0]; + } catch (error) { + if (error instanceof NoContainingScopeError) { + return undefined; + } + throw error; + } + } + + private getContainingLine(target: Target): Target { + return this.getContaining(target, { + type: "line", + })[0]; + } + + private getContaining(target: Target, scopeType: ScopeType): Target[] { + return this.modifierStageFactory + .create({ type: "containingScope", scopeType }) + .run(target); + } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts index 6f0eff3f9b..a99c85987e 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts @@ -8,6 +8,7 @@ import type { Target } from "../../../typings/target.types"; import type { InteriorTarget } from "../../targets"; import { BoundedParagraphTarget, + LineTarget, ParagraphTarget, TokenTarget, } from "../../targets"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/SurroundingPairInteriorScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/SurroundingPairInteriorScopeHandler.ts index feb91a40a5..9ab0538058 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/SurroundingPairInteriorScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/SurroundingPairInteriorScopeHandler.ts @@ -45,10 +45,6 @@ export class SurroundingPairInteriorScopeHandler extends BaseScopeHandler { ); for (const scope of scopes) { - if (this.scopeType.requireSingleLine && !scope.domain.isSingleLine) { - continue; - } - yield { editor, domain: scope.domain, From e7ea3c91f43652ef23046adb82d8e48911c9848d Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 30 Jan 2025 16:05:34 +0100 Subject: [PATCH 2/4] Update docs --- packages/cursorless-org-docs/src/docs/user/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-org-docs/src/docs/user/README.md b/packages/cursorless-org-docs/src/docs/user/README.md index 89be87f7d5..652d0d1b51 100644 --- a/packages/cursorless-org-docs/src/docs/user/README.md +++ b/packages/cursorless-org-docs/src/docs/user/README.md @@ -314,7 +314,7 @@ The modifiers `"head"` and `"tail"` can be used to expand a target through the b - `"take head air"`: selects the mark through to start of the line - `"take tail air"`: selects the mark through to the end of the line -When inside a single-line surrounding pair (eg parentheses, brackets, etc) the head/tail modifier will only expand to the interior of that pair instead of the whole line. You can explicitly say `"head line"` or `"tail line"` to get the line behavior. +When inside a surrounding pair (eg parentheses, brackets, etc) the head/tail modifier will only expand to the interior of that pair instead of the whole line. You can explicitly say `"head line"` or `"tail line"` to get the line behavior. When followed by a modifier, they will expand their input to the start or end of the given modifier range. For example: From 9de4fdb3a11001af7b2d478609696b3b9860011b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 30 Jan 2025 16:07:09 +0100 Subject: [PATCH 3/4] cleanup --- .../modifiers/scopeHandlers/BoundedScopeHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts index a99c85987e..6f0eff3f9b 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts @@ -8,7 +8,6 @@ import type { Target } from "../../../typings/target.types"; import type { InteriorTarget } from "../../targets"; import { BoundedParagraphTarget, - LineTarget, ParagraphTarget, TokenTarget, } from "../../targets"; From c3479f6f98111045c7de958331cee3a97c7fecd9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 30 Jan 2025 16:08:36 +0100 Subject: [PATCH 4/4] More clean up --- .../src/processTargets/modifiers/HeadTailStage.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts index de5adc5dea..ea66c39525 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts @@ -57,13 +57,10 @@ class BoundedLineStage implements ModifierStage { const line = this.getContainingLine(target); const pairInterior = this.getContainingPairInterior(target); - if (pairInterior == null) { - return [line]; - } - - const intersection = line.contentRange.intersection( - pairInterior.contentRange, - ); + const intersection = + pairInterior != null + ? line.contentRange.intersection(pairInterior.contentRange) + : null; if (intersection == null || intersection.isEmpty) { return [line];