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, flatmap, 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,102 @@ function generateScopesExclusive(
? "disallowed"
: "disallowedIfStrict";

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

const interiorRanges = getExcludedInteriorRanges(
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 the interior scope range(s) within the containing scope of
* {@link initialPosition} that should be used to exclude next / previous
* scopes.
*
* The idea is that when you're in the headline of an if statement / function /
* etc, you're thinking at the same level as that scope, so the next scope
* should be outside of it. But when you're inside the body, so the next scope
* should be within it.
*
* For example, in the following code:
*
* ```typescript
* if (aaa) {
* bbb();
* ccc();
* }
* ddd();
* ```
*
* The target `"next state air"` should refer to `ddd();`, not `bbb();`.
* However, `"next state bat"` should refer to `ccc();`.
*/
function getExcludedInteriorRanges(
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 relevantScopes = ifilter(
interiorScopes,
(s) => !s.domain.contains(initialPosition),
);
const targets = flatmap(relevantScopes, (s) => s.getTargets(false));
return Array.from(imap(targets, (t) => t.contentRange));
}
Loading