Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions data/fixtures/recorded/relativeScopes/changeNextState.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
languageId: typescript
command:
version: 7
spokenForm: change next state
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: relativeScope
scopeType: {type: statement}
offset: 1
length: 1
direction: forward
usePrePhraseSnapshot: false
initialState:
documentContents: |-
if (true) {
const a = 1;
}
const b = 2;
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |
if (true) {
const a = 1;
}
selections:
- anchor: {line: 3, character: 0}
active: {line: 3, character: 0}
34 changes: 34 additions & 0 deletions data/fixtures/recorded/relativeScopes/changeNextState2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
languageId: typescript
command:
version: 7
spokenForm: change next state
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: relativeScope
scopeType: {type: statement}
offset: 1
length: 1
direction: forward
usePrePhraseSnapshot: false
initialState:
documentContents: |-
if (true) {

const a = 1;
}
selections:
- anchor: {line: 1, character: 3}
active: {line: 1, character: 3}
marks: {}
finalState:
documentContents: |-
if (true) {


}
selections:
- anchor: {line: 2, character: 3}
active: {line: 2, character: 3}
34 changes: 34 additions & 0 deletions data/fixtures/recorded/relativeScopes/changePreviousState.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
languageId: typescript
command:
version: 7
spokenForm: change previous state
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: relativeScope
scopeType: {type: statement}
offset: 1
length: 1
direction: backward
usePrePhraseSnapshot: false
initialState:
documentContents: |-
const b = 2;
if (true) {
const a = 1;
}
selections:
- anchor: {line: 3, character: 1}
active: {line: 3, character: 1}
marks: {}
finalState:
documentContents: |-

if (true) {
const a = 1;
}
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
36 changes: 36 additions & 0 deletions data/fixtures/recorded/relativeScopes/changePreviousState2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
languageId: typescript
command:
version: 7
spokenForm: change previous state
action:
name: clearAndSetSelection
target:
type: primitive
modifiers:
- type: relativeScope
scopeType: {type: statement}
offset: 1
length: 1
direction: backward
usePrePhraseSnapshot: false
initialState:
documentContents: |-
const b = 2;
if (true) {
const a = 1;

}
selections:
- anchor: {line: 3, character: 3}
active: {line: 3, character: 3}
marks: {}
finalState:
documentContents: |-
const b = 2;
if (true) {


}
selections:
- anchor: {line: 2, character: 3}
active: {line: 2, character: 3}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {
NoContainingScopeError,
type RelativeScopeModifier,
import type {
Direction,
Position,
Range,
RelativeScopeModifier,
TextEditor,
} from "@cursorless/common";
import { islice, itake } from "itertools";
import { NoContainingScopeError } from "@cursorless/common";
import { find, ifilter, imap, islice, itake } from "itertools";
import type { Target } from "../../typings/target.types";
import type { ModifierStage } from "../PipelineStages.types";
import { constructScopeRangeTarget } from "./constructScopeRangeTarget";
Expand Down Expand Up @@ -36,7 +40,12 @@ export class RelativeScopeStage implements ModifierStage {
const scopes = Array.from(
this.modifier.offset === 0
? generateScopesInclusive(scopeHandler, target, this.modifier)
: generateScopesExclusive(scopeHandler, target, this.modifier),
: generateScopesExclusive(
this.scopeHandlerFactory,
scopeHandler,
target,
this.modifier,
),
);

if (scopes.length < this.modifier.length) {
Expand Down Expand Up @@ -113,6 +122,7 @@ function generateScopesInclusive(
* first scope if input range is empty and is at start of that scope.
*/
function generateScopesExclusive(
scopeHandlerFactory: ScopeHandlerFactory,
scopeHandler: ScopeHandler,
target: Target,
modifier: RelativeScopeModifier,
Expand All @@ -130,12 +140,84 @@ function generateScopesExclusive(
? "disallowed"
: "disallowedIfStrict";

return islice(
let scopes = scopeHandler.generateScopes(editor, initialPosition, direction, {
containment,
skipAncestorScopes: true,
});

const interiorRanges = getInteriorScopeRanges(
scopeHandlerFactory,
scopeHandler,
editor,
initialPosition,
direction,
);

if (interiorRanges != null) {
scopes = ifilter(
scopes,
(s) => !interiorRanges.some((r) => r.contains(s.domain)),
);
}

return islice(scopes, offset - 1, offset + desiredScopeCount - 1);
}

/**
* Gets interior scope ranges within the containing scope at the given position.
* These are used to filter out scopes that are within interior scopes when
* applying relative scope modifiers.
*/
function getInteriorScopeRanges(
scopeHandlerFactory: ScopeHandlerFactory,
scopeHandler: ScopeHandler,
editor: TextEditor,
initialPosition: Position,
direction: Direction,
): Range[] | undefined {
const containingScope = find(
scopeHandler.generateScopes(editor, initialPosition, direction, {
containment,
containment: "required",
allowAdjacentScopes: true,
skipAncestorScopes: true,
}),
offset - 1,
offset + desiredScopeCount - 1,
);

if (containingScope == null) {
return undefined;
}

const interiorScopeHandler = scopeHandlerFactory.create(
{ type: "interior" },
editor.document.languageId,
);

const containingInitialPosition =
direction === "forward"
? containingScope.domain.start
: containingScope.domain.end;
const containingDistalPosition =
direction === "forward"
? containingScope.domain.end
: containingScope.domain.start;

const interiorScopes = interiorScopeHandler.generateScopes(
editor,
containingInitialPosition,
direction,
{
skipAncestorScopes: true,
distalPosition: containingDistalPosition,
},
);

// Interiors containing the initial position are excluded. This happens when
// you are in the body of an if statement and use `next state` and in that
// case we don't want to exclude scopes within the same interior.
const interiorRangesIt = imap(
ifilter(interiorScopes, (s) => !s.domain.contains(initialPosition)),
(s) => s.domain,
);
const interiorRanges = Array.from(interiorRangesIt);
return interiorRanges.length > 0 ? interiorRanges : undefined;
}
Loading