diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts index 9dc97beee3..918bf03aad 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts @@ -1,12 +1,20 @@ -import type { RelativeScopeModifier } from "@cursorless/common"; +import { + TextEditor, + type Range, + type RelativeScopeModifier, +} from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; import { constructScopeRangeTarget } from "./constructScopeRangeTarget"; +import { getContainingScopeTarget } from "./getContainingScopeTarget"; import { runLegacy } from "./relativeScopeLegacy"; import { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; import { TargetScope } from "./scopeHandlers/scope.types"; -import type { ContainmentPolicy } from "./scopeHandlers/scopeHandler.types"; +import type { + ContainmentPolicy, + ScopeHandler, +} from "./scopeHandlers/scopeHandler.types"; import { OutOfRangeError } from "./targetSequenceUtils"; /** @@ -32,51 +40,188 @@ export class RelativeExclusiveScopeStage implements ModifierStage { return runLegacy(this.modifierStageFactory, this.modifier, target); } - const { isReversed, editor, contentRange: inputRange } = target; - const { length: desiredScopeCount, direction, offset } = this.modifier; - - const initialPosition = - direction === "forward" ? inputRange.end : inputRange.start; - - // If inputRange is empty, then we skip past any scopes that start at - // inputRange. Otherwise just disallow any scopes that start strictly - // before the end of input range (strictly after for "backward"). - const containment: ContainmentPolicy | undefined = inputRange.isEmpty - ? "disallowed" - : "disallowedIfStrict"; - - let scopeCount = 0; - let proximalScope: TargetScope | undefined; - for (const scope of scopeHandler.generateScopes( - editor, - initialPosition, - direction, - { containment, skipAncestorScopes: true }, - )) { - scopeCount += 1; - - if (scopeCount < offset) { - // Skip until we hit `offset` - continue; - } + let targets = scopeHandler.isHierarchical + ? getTargetsForIterationScope( + this.scopeHandlerFactory, + scopeHandler, + target, + this.modifier, + ) + : undefined; + + if (targets == null) { + targets = getTargetsForPosition(scopeHandler, target, this.modifier); + } + + if (targets != null) { + return targets; + } + + throw new OutOfRangeError(); + } +} + +function getTargetsForIterationScope( + scopeHandlerFactory: ScopeHandlerFactory, + scopeHandler: ScopeHandler, + target: Target, + modifier: RelativeScopeModifier, +): Target[] | undefined { + const { contentRange } = target; + const isForward = modifier.direction === "forward"; + const initialPosition = isForward ? contentRange.end : contentRange.start; + + let scopes = getDefaultIterationRange( + scopeHandler, + scopeHandlerFactory, + target, + )?.flatMap((iterationRange) => + getScopesOverlappingRange(scopeHandler, target.editor, iterationRange), + ); + + if (scopes == null) { + return undefined; + } - if (scopeCount === offset) { - // When we hit offset, that becomes proximal scope - if (desiredScopeCount === 1) { - // Just yield it if we only want 1 scope - return scope.getTargets(isReversed); - } + if (!isForward) { + scopes = scopes.reverse(); + } - proximalScope = scope; - continue; + const index = (() => { + if (contentRange.isEmpty) { + if (isForward) { + return scopes.findIndex((scope) => + scope.domain.start.isAfter(initialPosition), + ); } + return scopes.findIndex((scope) => + scope.domain.end.isBefore(initialPosition), + ); + } + if (isForward) { + return scopes.findIndex((scope) => + scope.domain.start.isAfterOrEqual(initialPosition), + ); + } + return scopes.findIndex((scope) => + scope.domain.end.isBeforeOrEqual(initialPosition), + ); + })(); + + if (index < 0) { + return undefined; + } + + scopes = scopes.slice(index); + + return scopesToTargets(scopes, target, modifier); +} + +function getTargetsForPosition( + scopeHandler: ScopeHandler, + target: Target, + modifier: RelativeScopeModifier, +): Target[] | undefined { + const { editor, contentRange } = target; + const { direction } = modifier; + + const initialPosition = + direction === "forward" ? contentRange.end : contentRange.start; - if (scopeCount === offset + desiredScopeCount - 1) { - // Then make a range when we get the desired number of scopes - return constructScopeRangeTarget(isReversed, proximalScope!, scope); + // If inputRange is empty, then we skip past any scopes that start at + // contentRange. Otherwise just disallow any scopes that start strictly + // before the end of input range (strictly after for "backward"). + const containment: ContainmentPolicy = contentRange.isEmpty + ? "disallowed" + : "disallowedIfStrict"; + + const scopes = scopeHandler.generateScopes( + editor, + initialPosition, + direction, + { + containment, + skipAncestorScopes: true, + }, + ); + + return scopesToTargets(scopes, target, modifier); +} + +function scopesToTargets( + scopes: Iterable, + target: Target, + modifier: RelativeScopeModifier, +): Target[] | undefined { + const { isReversed } = target; + const { length: desiredScopeCount, offset } = modifier; + + let scopeCount = 0; + let proximalScope: TargetScope | undefined; + + for (const scope of scopes) { + scopeCount += 1; + + if (scopeCount < offset) { + // Skip until we hit `offset` + continue; + } + + if (scopeCount === offset) { + // When we hit offset, that becomes proximal scope + if (desiredScopeCount === 1) { + // Just yield it if we only want 1 scope + return scope.getTargets(isReversed); } + + proximalScope = scope; + continue; } - throw new OutOfRangeError(); + if (scopeCount === offset + desiredScopeCount - 1) { + // Then make a range when we get the desired number of scopes + return constructScopeRangeTarget(isReversed, proximalScope!, scope); + } } + + return undefined; +} + +function getDefaultIterationRange( + scopeHandler: ScopeHandler, + scopeHandlerFactory: ScopeHandlerFactory, + target: Target, +): Range[] | undefined { + const iterationScopeHandler = scopeHandlerFactory.create( + scopeHandler.iterationScopeType, + target.editor.document.languageId, + ); + + if (iterationScopeHandler == null) { + return undefined; + } + + const iterationScopeTarget = getContainingScopeTarget( + target, + iterationScopeHandler, + ); + + if (iterationScopeTarget == null) { + return undefined; + } + + return iterationScopeTarget.map((target) => target.contentRange); +} + +function getScopesOverlappingRange( + scopeHandler: ScopeHandler, + editor: TextEditor, + { start, end }: Range, +): TargetScope[] { + return Array.from( + scopeHandler.generateScopes(editor, start, "forward", { + distalPosition: end, + skipAncestorScopes: true, + }), + ); } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts index 73528db44c..2c17639590 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts @@ -1,16 +1,22 @@ import { NoContainingScopeError, + Range, RelativeScopeModifier, + TextEditor, } from "@cursorless/common"; +import { itake } from "itertools"; import type { Target } from "../../typings/target.types"; import { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; import { constructScopeRangeTarget } from "./constructScopeRangeTarget"; +import { getContainingScopeTarget } from "./getContainingScopeTarget"; import { getPreferredScopeTouchingPosition } from "./getPreferredScopeTouchingPosition"; import { runLegacy } from "./relativeScopeLegacy"; import { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; -import { itake } from "itertools"; +import { TargetScope } from "./scopeHandlers/scope.types"; +import { ScopeHandler } from "./scopeHandlers/scopeHandler.types"; import { OutOfRangeError } from "./targetSequenceUtils"; +import { PlainTarget } from "../targets"; /** * Handles relative modifiers that include targets intersecting with the input, * eg `"two funks"`, `"token backward"`, etc. Proceeds as follows: @@ -45,30 +51,38 @@ export class RelativeInclusiveScopeStage implements ModifierStage { const { isReversed, editor, contentRange } = target; const { length: desiredScopeCount, direction } = this.modifier; - const initialRange = getPreferredScopeTouchingPosition( - scopeHandler, - editor, - direction === "forward" ? contentRange.start : contentRange.end, - direction, - )?.domain; + // const initialRange = getPreferredScopeTouchingPosition( + // scopeHandler, + // editor, + // direction === "forward" ? contentRange.start : contentRange.end, + // direction, + // )?.domain; + + const containingTargets = getContainingScopeTarget(target, scopeHandler); - if (initialRange == null) { + if (containingTargets) { throw new NoContainingScopeError(this.modifier.scopeType.type); } - const scopes = Array.from( - itake( - desiredScopeCount, - scopeHandler.generateScopes( + let it = scopeHandler.isHierarchical + ? getScopesForIterationScope( + this.scopeHandlerFactory, + scopeHandler, editor, - direction === "forward" ? initialRange.start : initialRange.end, - direction, - { - skipAncestorScopes: true, - }, - ), - ), - ); + initialRange, + this.modifier, + ) + : undefined; + + if (it == null) { + const initialPosition = + direction === "forward" ? initialRange.start : initialRange.end; + it = scopeHandler.generateScopes(editor, initialPosition, direction, { + skipAncestorScopes: true, + }); + } + + const scopes = Array.from(itake(desiredScopeCount, it)); if (scopes.length < desiredScopeCount) { throw new OutOfRangeError(); @@ -81,3 +95,145 @@ export class RelativeInclusiveScopeStage implements ModifierStage { ); } } + +function* getScopesForIterationScope( + scopeHandlerFactory: ScopeHandlerFactory, + scopeHandler: ScopeHandler, + editor: TextEditor, + range: Range, + modifier: RelativeScopeModifier, +): Iterable | undefined { + const { direction } = modifier; + const isForward = direction === "forward"; + const initialPosition = isForward ? range.start : range.end; + + const iterationScopeHandler = scopeHandlerFactory.create( + scopeHandler.iterationScopeType, + editor.document.languageId, + ); + + if (iterationScopeHandler == null) { + return undefined; + } + + // const iterationScopeTarget = getContainingScopeTarget( + // target, + // iterationScopeHandler, + // ); + + // if (iterationScopeTarget == null) { + // return undefined; + // } + + const initialIterationRange = getPreferredScopeTouchingPosition( + iterationScopeHandler, + editor, + initialPosition, + direction, + )?.domain; + + if (initialIterationRange == null) { + return undefined; + } + + const iterationScopes = iterationScopeHandler.generateScopes( + editor, + initialPosition, + direction, + { skipAncestorScopes: true, allowAdjacentScopes: true }, + ); + + for (const iterationScope of iterationScopes) { + for (const iterationTarget of iterationScope.getTargets(false)) { + console.log("iter", iterationTarget.contentRange.toString()); + const { start, end } = iterationTarget.contentRange; + const proximalPosition = isForward ? start : end; + const distalPosition = isForward ? end : start; + const scopes = scopeHandler.generateScopes( + editor, + proximalPosition, + direction, + { skipAncestorScopes: true, distalPosition }, + ); + for (const scope of scopes) { + console.log("scope", scope.domain.toString()); + yield scope; + } + } + } + + let scopes = getDefaultIterationRanges( + scopeHandler, + scopeHandlerFactory, + editor, + range, + )?.flatMap((iterationRange) => + getScopesOverlappingRange(scopeHandler, editor, iterationRange), + ); + + if (scopes == null) { + return undefined; + } + + if (!isForward) { + scopes = scopes.reverse(); + } + + const index = scopes.findIndex((scope) => + scope.domain.contains(initialPosition), + ); + + if (index < 0) { + return undefined; + } + + return scopes.slice(index); +} + +function getDefaultIterationRanges( + scopeHandler: ScopeHandler, + scopeHandlerFactory: ScopeHandlerFactory, + editor: TextEditor, + range: Range, +): Range[] | undefined { + const iterationScopeHandler = scopeHandlerFactory.create( + scopeHandler.iterationScopeType, + editor.document.languageId, + ); + + if (iterationScopeHandler == null) { + return undefined; + } + + const iterationScopeTarget = getContainingScopeTarget( + new PlainTarget({ editor, contentRange: range, isReversed: false }), + iterationScopeHandler, + ); + + if (iterationScopeTarget == null) { + return undefined; + } + + return iterationScopeTarget.map((target) => target.contentRange); +} + +function getScopesOverlappingRange( + scopeHandler: ScopeHandler, + editor: TextEditor, + { start, end }: Range, +): TargetScope[] { + return Array.from( + scopeHandler.generateScopes(editor, start, "forward", { + distalPosition: end, + skipAncestorScopes: true, + }), + ); +} + +export function ensureSingleTarget(targets: Target[]): Target { + if (targets.length !== 1) { + throw new Error("Can only have one target with this modifier"); + } + + return targets[0]; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.test.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.test.ts index 3e5d89e3df..fd43f81555 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.test.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.test.ts @@ -17,7 +17,7 @@ class TestScopeHandler extends BaseScopeHandler { constructor( private scopes: TargetScope[], - protected isHierarchical: boolean, + public isHierarchical: boolean, ) { super(); } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts index aedfd826e9..8dd45b47fd 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts @@ -23,15 +23,10 @@ const DEFAULT_REQUIREMENTS: Omit = export abstract class BaseScopeHandler implements ScopeHandler { public abstract readonly scopeType: ScopeType | undefined; public abstract readonly iterationScopeType: ScopeType | CustomScopeType; + public abstract readonly isHierarchical: boolean; public readonly includeAdjacentInEvery: boolean = false; - /** - * Indicates whether scopes are allowed to contain one another. If `false`, we - * can optimise the algorithm by making certain assumptions. - */ - protected abstract readonly isHierarchical: boolean; - /** * Returns an iterable that yields scopes. * diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts index f9391f354b..976ca0f628 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts @@ -7,7 +7,7 @@ import { TargetScope } from "./scope.types"; export class DocumentScopeHandler extends BaseScopeHandler { public readonly scopeType = { type: "document" } as const; public readonly iterationScopeType = { type: "document" } as const; - protected readonly isHierarchical = false; + public readonly isHierarchical = false; constructor(_scopeType: ScopeType, _languageId: string) { super(); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts index e57b1094c5..5977ac4c08 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts @@ -14,7 +14,7 @@ export class LineScopeHandler extends BaseScopeHandler { public readonly iterationScopeType: ScopeType = { type: "paragraph", } as const; - protected readonly isHierarchical = false; + public readonly isHierarchical = false; public readonly includeAdjacentInEvery: boolean = true; constructor(_scopeType: ScopeType, _languageId: string) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NestedScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NestedScopeHandler.ts index 6e4d396354..a2e9b3e408 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NestedScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NestedScopeHandler.ts @@ -23,7 +23,7 @@ import type { */ export abstract class NestedScopeHandler extends BaseScopeHandler { public abstract readonly iterationScopeType: ScopeType; - protected readonly isHierarchical = false; + public readonly isHierarchical = false; /** * We expand to this scope type before looking for instances of the scope type diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/OneOfScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/OneOfScopeHandler.ts index ea2d82bb2b..34241760b8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/OneOfScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/OneOfScopeHandler.ts @@ -16,7 +16,7 @@ import { } from "./scopeHandler.types"; export class OneOfScopeHandler extends BaseScopeHandler { - protected isHierarchical = true; + public isHierarchical = true; static create( scopeHandlerFactory: ScopeHandlerFactory, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ParagraphScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ParagraphScopeHandler.ts index d61cdd1f52..c363dbe1aa 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ParagraphScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ParagraphScopeHandler.ts @@ -14,7 +14,7 @@ import { TargetScope } from "./scope.types"; export class ParagraphScopeHandler extends BaseScopeHandler { public readonly scopeType: ScopeType = { type: "paragraph" }; public readonly iterationScopeType: ScopeType = { type: "document" }; - protected readonly isHierarchical = false; + public readonly isHierarchical = false; constructor(_scopeType: ScopeType, _languageId: string) { super(); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler.ts index b51a6389a1..74a0f7d2ed 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler.ts @@ -7,7 +7,7 @@ import { ScopeIteratorRequirements } from "./scopeHandler.types"; export class SurroundingPairScopeHandler extends BaseScopeHandler { public readonly iterationScopeType; - protected isHierarchical = true; + public isHierarchical = true; constructor( public readonly scopeType: SurroundingPairScopeType, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts index 7b0a044103..17c5d25b0f 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts @@ -10,7 +10,7 @@ import { getRelatedCapture, getRelatedRange } from "./captureUtils"; /** Scope handler to be used for iteration scopes of tree-sitter scope types */ export class TreeSitterIterationScopeHandler extends BaseTreeSitterScopeHandler { - protected isHierarchical = true; + public isHierarchical = true; // Doesn't correspond to any scope type public scopeType = undefined; 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 e5d054ee63..873b36b2a8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts @@ -14,7 +14,7 @@ import { findCaptureByName, getRelatedRange } from "./captureUtils"; * Handles scopes that are implemented using tree-sitter. */ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { - protected isHierarchical = true; + public isHierarchical = true; constructor( query: TreeSitterQuery, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterTextFragmentScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterTextFragmentScopeHandler.ts index a7fb8609dd..a1eb463203 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterTextFragmentScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterTextFragmentScopeHandler.ts @@ -12,7 +12,7 @@ import { findCaptureByName } from "./captureUtils"; /** Scope handler to be used for extracting text fragments from the perspective * of surrounding pairs */ export class TreeSitterTextFragmentScopeHandler extends BaseTreeSitterScopeHandler { - protected isHierarchical = true; + public isHierarchical = true; // Doesn't correspond to any scope type public scopeType = undefined; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts index 730adf60e4..a1ebba07dd 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts @@ -87,6 +87,12 @@ export interface ScopeHandler { * range is at the beginning or end of the line. */ readonly includeAdjacentInEvery: boolean; + + /** + * Indicates whether scopes are allowed to contain one another. If `false`, we + * can optimise the algorithm by making certain assumptions. + */ + readonly isHierarchical: boolean; } export type ContainmentPolicy = diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState.yml new file mode 100644 index 0000000000..dc8dac6b64 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState.yml @@ -0,0 +1,35 @@ +languageId: typescript +command: + version: 6 + spokenForm: change next state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: statement} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + if (true) { + console.log(1) + } + + console.log(2) + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |+ + if (true) { + console.log(1) + } + + selections: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState2.yml new file mode 100644 index 0000000000..1f7821b291 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState2.yml @@ -0,0 +1,35 @@ +languageId: typescript +command: + version: 6 + spokenForm: change next state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: statement} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + if (true) { + console.log(1) + } + + console.log(2) + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + marks: {} +finalState: + documentContents: |+ + if (true) { + console.log(1) + } + + selections: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState3.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState3.yml new file mode 100644 index 0000000000..8fae422ef1 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextState3.yml @@ -0,0 +1,36 @@ +languageId: typescript +command: + version: 6 + spokenForm: change next state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: statement} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + if (true) { + console.log(1) + + console.log(2) + } + selections: + - anchor: {line: 2, character: 4} + active: {line: 2, character: 4} + marks: {} +finalState: + documentContents: |- + if (true) { + console.log(1) + + + } + selections: + - anchor: {line: 3, character: 4} + active: {line: 3, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextStatePreviousState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextStatePreviousState.yml new file mode 100644 index 0000000000..f0a69cc835 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeNextStatePreviousState.yml @@ -0,0 +1,38 @@ +languageId: python +command: + version: 6 + spokenForm: change next state previous state + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: statement} + offset: 1 + length: 1 + direction: forward + - type: relativeScope + scopeType: {type: statement} + offset: 1 + length: 1 + direction: backward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(): + bbb = 0 + + ddd = 0 + selections: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} + marks: {} +finalState: + documentContents: |+ + def aaa(): + bbb = 0 + + selections: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeTwoStatesBackward.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeTwoStatesBackward.yml new file mode 100644 index 0000000000..cf1bcacad8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/changeTwoStatesBackward.yml @@ -0,0 +1,31 @@ +languageId: python +command: + version: 6 + spokenForm: change two states backward + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: statement} + offset: 0 + length: 2 + direction: backward + usePrePhraseSnapshot: true +initialState: + documentContents: |- + def aaa(): + bbb = 0 + ccc = 0 + + ddd = 0 + selections: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} + marks: {} +finalState: + documentContents: "" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearPreviousState.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearPreviousState.yml index efd71ddb81..41197ced6f 100644 --- a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearPreviousState.yml +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/relativeScopes/clearPreviousState.yml @@ -21,9 +21,8 @@ initialState: active: {line: 2, character: 0} marks: {} finalState: - documentContents: | - if True: - + documentContents: |+ + selections: - - anchor: {line: 1, character: 4} - active: {line: 1, character: 4} + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/queries/javascript.core.scm b/queries/javascript.core.scm index d69ee12fca..8e12a46b06 100644 --- a/queries/javascript.core.scm +++ b/queries/javascript.core.scm @@ -681,6 +681,13 @@ (#not-parent-type? @statement export_statement) ) +(program) @statement.iteration + +(statement_block + "{" @statement.iteration.start.endOf + "}" @statement.iteration.end.startOf +) + ;;!! foo(name: string) {} ;;! ^^^^^^^^^^^^ (