Skip to content

Commit 7cc3f73

Browse files
Update relative scope stage to skip containing range when interior is missing (#3123)
1 parent 2a0ccd6 commit 7cc3f73

File tree

3 files changed

+108
-69
lines changed

3 files changed

+108
-69
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: change next call
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: relativeScope
11+
scopeType: {type: functionCall}
12+
offset: 1
13+
length: 1
14+
direction: forward
15+
usePrePhraseSnapshot: false
16+
initialState:
17+
documentContents: aaa(bbb()); ccc();
18+
selections:
19+
- anchor: {line: 0, character: 0}
20+
active: {line: 0, character: 0}
21+
marks: {}
22+
finalState:
23+
documentContents: aaa(bbb()); ;
24+
selections:
25+
- anchor: {line: 0, character: 12}
26+
active: {line: 0, character: 12}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: change previous call
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: relativeScope
11+
scopeType: {type: functionCall}
12+
offset: 1
13+
length: 1
14+
direction: backward
15+
usePrePhraseSnapshot: false
16+
initialState:
17+
documentContents: aaa(); bbb(ccc());
18+
selections:
19+
- anchor: {line: 0, character: 17}
20+
active: {line: 0, character: 17}
21+
marks: {}
22+
finalState:
23+
documentContents: ; bbb(ccc());
24+
selections:
25+
- anchor: {line: 0, character: 0}
26+
active: {line: 0, character: 0}

packages/cursorless-engine/src/processTargets/modifiers/RelativeScopeStage.ts

Lines changed: 56 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ function generateScopesExclusive(
165165
}
166166

167167
/**
168-
* Gets the interior scope range(s) within the containing scope of
168+
* Gets the scope range(s) within the containing scope of
169169
* {@link initialPosition} that should be used to exclude next / previous
170170
* scopes.
171171
*
@@ -194,114 +194,101 @@ function getExcludedInteriorRanges(
194194
initialPosition: Position,
195195
direction: Direction,
196196
): Range[] {
197-
const interiorTargets =
198-
scopeHandler.scopeType?.type === "surroundingPair"
199-
? getSurroundingPairInteriorTargets(
200-
scopeHandler,
201-
editor,
202-
initialPosition,
203-
direction,
204-
)
205-
: getLanguageInteriorTargets(
206-
scopeHandlerFactory,
207-
scopeHandler,
208-
editor,
209-
initialPosition,
210-
direction,
211-
);
212-
213-
// Interiors containing the initial position are excluded. This happens when
214-
// you are in the body of an if statement and use `next state` and in that
215-
// case we don't want to exclude scopes within the same interior.
216-
return interiorTargets
217-
.map((t) =>
218-
t instanceof InteriorTarget ? t.fullInteriorRange : t.contentRange,
219-
)
220-
.filter((r) => !r.contains(initialPosition));
221-
}
222-
223-
function getSurroundingPairInteriorTargets(
224-
scopeHandler: ScopeHandler,
225-
editor: TextEditor,
226-
initialPosition: Position,
227-
direction: Direction,
228-
): Target[] {
229-
const containingScope = getContainingScope(
197+
const containingScopeTarget = getContainingScopeTarget(
230198
scopeHandler,
231199
editor,
232200
initialPosition,
233201
direction,
234202
);
235203

236-
if (containingScope == null) {
204+
// No containing scope, nothing to exclude.
205+
if (containingScopeTarget == null) {
237206
return [];
238207
}
239208

240-
return containingScope
241-
.getTargets(false)
242-
.flatMap((t) => t.getInterior() ?? []);
243-
}
209+
const containingInteriorTargets = containingScopeTarget.getInterior();
244210

245-
function getLanguageInteriorTargets(
246-
scopeHandlerFactory: ScopeHandlerFactory,
247-
scopeHandler: ScopeHandler,
248-
editor: TextEditor,
249-
initialPosition: Position,
250-
direction: Direction,
251-
): Target[] {
211+
// Containing target already has an interior. eg a surrounding pair scope.
212+
if (containingInteriorTargets != null) {
213+
return getFilteredInteriorRanges(
214+
containingInteriorTargets,
215+
initialPosition,
216+
);
217+
}
218+
219+
// Fallback to language specific interior scope handler
252220
const interiorScopeHandler = scopeHandlerFactory.maybeCreate(
253221
{ type: "interior" },
254222
editor.document.languageId,
255223
);
256224

225+
// No interior scope handler, nothing to exclude.
226+
// For languages that hasn't defined the interior scope handler yet we default
227+
// to NOT excluding anything.
257228
if (interiorScopeHandler == null) {
258229
return [];
259230
}
260231

261-
const containingScope = getContainingScope(
262-
scopeHandler,
263-
editor,
264-
initialPosition,
232+
const containingPositions = getPositions(
233+
containingScopeTarget.contentRange,
265234
direction,
266235
);
267236

268-
if (containingScope == null) {
269-
return [];
270-
}
271-
272-
const containingInitialPosition =
273-
direction === "forward"
274-
? containingScope.domain.start
275-
: containingScope.domain.end;
276-
const containingDistalPosition =
277-
direction === "forward"
278-
? containingScope.domain.end
279-
: containingScope.domain.start;
280-
281237
const interiorScopes = interiorScopeHandler.generateScopes(
282238
editor,
283-
containingInitialPosition,
239+
containingPositions.initial,
284240
direction,
285241
{
286242
skipAncestorScopes: true,
287-
distalPosition: containingDistalPosition,
243+
distalPosition: containingPositions.distal,
288244
},
289245
);
290246

291-
return Array.from(interiorScopes).flatMap((s) => s.getTargets(false));
247+
const interiorTargets = Array.from(interiorScopes).flatMap((s) =>
248+
s.getTargets(false),
249+
);
250+
251+
if (interiorTargets.length > 0) {
252+
return getFilteredInteriorRanges(interiorTargets, initialPosition);
253+
}
254+
255+
// This containing scope has no interior.
256+
// Default to excluding the entire containing scope.
257+
return [containingScopeTarget.contentRange];
258+
}
259+
260+
function getPositions(range: Range, direction: Direction) {
261+
return direction === "forward"
262+
? { initial: range.start, distal: range.end }
263+
: { initial: range.end, distal: range.start };
264+
}
265+
266+
function getFilteredInteriorRanges(
267+
interiorTargets: Target[],
268+
initialPosition: Position,
269+
) {
270+
// Interiors containing the initial position are excluded. This happens when
271+
// you are in the body of an if statement and use `next state` and in that
272+
// case we don't want to exclude scopes within the same interior.
273+
return interiorTargets
274+
.map((t) =>
275+
t instanceof InteriorTarget ? t.fullInteriorRange : t.contentRange,
276+
)
277+
.filter((r) => !r.contains(initialPosition));
292278
}
293279

294-
function getContainingScope(
280+
function getContainingScopeTarget(
295281
scopeHandler: ScopeHandler,
296282
editor: TextEditor,
297283
initialPosition: Position,
298284
direction: Direction,
299-
): TargetScope | undefined {
300-
return find(
285+
): Target | undefined {
286+
const containingScope = find(
301287
scopeHandler.generateScopes(editor, initialPosition, direction, {
302288
containment: "required",
303289
allowAdjacentScopes: true,
304290
skipAncestorScopes: true,
305291
}),
306292
);
293+
return containingScope?.getTargets(false)[0];
307294
}

0 commit comments

Comments
 (0)