Skip to content

Commit 5e5562b

Browse files
committed
Cleanup
1 parent 5ba6384 commit 5e5562b

File tree

9 files changed

+163
-109
lines changed

9 files changed

+163
-109
lines changed

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { ContainingScopeModifier } from "@cursorless/common";
1+
import {
2+
ContainingScopeModifier,
3+
NoContainingScopeError,
4+
} from "@cursorless/common";
25
import type { ProcessedTargetsContext } from "../../typings/Types";
36
import type { Target } from "../../typings/target.types";
47
import { ModifierStageFactory } from "../ModifierStageFactory";
@@ -44,13 +47,17 @@ export class ContainingScopeStage implements ModifierStage {
4447
.getLegacyScopeStage(this.modifier)
4548
.run(context, target);
4649
}
47-
const errorName = this.modifier.scopeType.type;
4850

49-
return getContainingScopeTarget(
51+
const containingScope = getContainingScopeTarget(
5052
target,
5153
scopeHandler,
52-
errorName,
5354
ancestorIndex,
5455
);
56+
57+
if (containingScope == null) {
58+
throw new NoContainingScopeError(this.modifier.scopeType.type);
59+
}
60+
61+
return [containingScope];
5562
}
5663
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,17 @@ export class EveryScopeStage implements ModifierStage {
107107
throw Error("Could not find iteration scope handler");
108108
}
109109

110-
return getContainingScopeTarget(
110+
const iterationScopeTarget = getContainingScopeTarget(
111111
target,
112112
iterationScopeHandler,
113-
`iteration scope for ${scopeHandler.scopeType?.type ?? "unknown"}`,
114-
)[0].contentRange;
113+
);
114+
115+
if (iterationScopeTarget == null) {
116+
throw new NoContainingScopeError(
117+
`iteration scope for ${scopeHandler.iterationScopeType.type}`,
118+
);
119+
}
120+
121+
return iterationScopeTarget.contentRange;
115122
}
116123
}

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import {
2-
Direction,
3-
NoContainingScopeError,
4-
Position,
5-
TextEditor,
6-
} from "@cursorless/common";
1+
import { Direction, Position, TextEditor } from "@cursorless/common";
72
import type { Target } from "../../typings/target.types";
83
import { constructScopeRangeTarget } from "./constructScopeRangeTarget";
94
import { getContainingScope } from "./getContainingScope";
105
import { TargetScope } from "./scopeHandlers/scope.types";
116
import { ScopeHandler } from "./scopeHandlers/scopeHandler.types";
127

8+
/**
9+
* Finds the containing scope of the target for the given scope handler
10+
*
11+
* @param target The target to find the containing scope of
12+
* @param scopeHandler The scope handler for the scope type to find containing scope of
13+
* @param ancestorIndex How many ancestors to go up. 0 means the immediate containing scope
14+
* @returns A target representing the containing scope, or undefined if no containing scope found
15+
*/
1316
export function getContainingScopeTarget(
1417
target: Target,
1518
scopeHandler: ScopeHandler,
16-
errorName: string,
1719
ancestorIndex: number = 0,
18-
) {
20+
): Target | undefined {
1921
const {
2022
isReversed,
2123
editor,
@@ -27,7 +29,7 @@ export function getContainingScopeTarget(
2729
let scope = getPreferredScopeTouchingPosition(scopeHandler, editor, start);
2830

2931
if (scope == null) {
30-
throw new NoContainingScopeError(errorName);
32+
return undefined;
3133
}
3234

3335
if (ancestorIndex > 0) {
@@ -41,10 +43,10 @@ export function getContainingScopeTarget(
4143
}
4244

4345
if (scope == null) {
44-
throw new NoContainingScopeError(errorName);
46+
return undefined;
4547
}
4648

47-
return [scope.getTarget(isReversed)];
49+
return scope.getTarget(isReversed);
4850
}
4951

5052
const startScope = expandFromPosition(
@@ -56,11 +58,11 @@ export function getContainingScopeTarget(
5658
);
5759

5860
if (startScope == null) {
59-
throw new NoContainingScopeError(errorName);
61+
return undefined;
6062
}
6163

6264
if (startScope.domain.contains(end)) {
63-
return [startScope.getTarget(isReversed)];
65+
return startScope.getTarget(isReversed);
6466
}
6567

6668
const endScope = expandFromPosition(
@@ -72,11 +74,12 @@ export function getContainingScopeTarget(
7274
);
7375

7476
if (endScope == null) {
75-
throw new NoContainingScopeError(errorName);
77+
return undefined;
7678
}
7779

78-
return [constructScopeRangeTarget(isReversed, startScope, endScope)];
80+
return constructScopeRangeTarget(isReversed, startScope, endScope);
7981
}
82+
8083
function expandFromPosition(
8184
scopeHandler: ScopeHandler,
8285
editor: TextEditor,
@@ -100,6 +103,7 @@ function expandFromPosition(
100103

101104
return undefined;
102105
}
106+
103107
function getPreferredScopeTouchingPosition(
104108
scopeHandler: ScopeHandler,
105109
editor: TextEditor,

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import { Direction, Position, TextEditor } from "@cursorless/common";
1+
import {
2+
Direction,
3+
Position,
4+
TextDocument,
5+
TextEditor,
6+
} from "@cursorless/common";
27
import { Query, QueryMatch } from "web-tree-sitter";
38
import { TreeSitter } from "../../../..";
9+
import BaseScopeHandler from "../BaseScopeHandler";
410
import { compareTargetScopes } from "../compareTargetScopes";
511
import { TargetScope } from "../scope.types";
612
import { ScopeIteratorRequirements } from "../scopeHandler.types";
7-
import { getQueryRange } from "./getQueryRange";
8-
import BaseScopeHandler from "../BaseScopeHandler";
9-
import { positionToPoint } from "./getCaptureRangeByName";
13+
import { positionToPoint } from "./captureUtils";
1014

15+
/** Base scope handler to use for both tree-sitter scopes and their iteration scopes */
1116
export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler {
1217
constructor(protected treeSitter: TreeSitter, protected query: Query) {
1318
super();
@@ -22,7 +27,12 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler {
2227
const { document } = editor;
2328

2429
/** Narrow the range within which tree-sitter searches, for performance */
25-
const { start, end } = getQueryRange(document, position, direction, hints);
30+
const { start, end } = getQuerySearchRange(
31+
document,
32+
position,
33+
direction,
34+
hints,
35+
);
2636

2737
yield* this.query
2838
.matches(
@@ -35,8 +45,70 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler {
3545
.sort((a, b) => compareTargetScopes(direction, position, a, b));
3646
}
3747

48+
/**
49+
* Convert a tree-sitter match to a scope, or undefined if the match is not
50+
* relevant to this scope handler
51+
* @param editor The editor in which the match was found
52+
* @param match The match to convert to a scope
53+
* @returns The scope, or undefined if the match is not relevant to this scope
54+
* handler
55+
*/
3856
protected abstract matchToScope(
3957
editor: TextEditor,
4058
match: QueryMatch,
4159
): TargetScope | undefined;
4260
}
61+
62+
/**
63+
* Constructs a range to pass to {@link Query.matches} to find scopes. Note
64+
* that {@link Query.matches} will only return scopes that have non-empty
65+
* intersection with this range. Also note that the base
66+
* {@link BaseScopeHandler.generateScopes} will filter out any extra scopes
67+
* that we yield, so we don't need to be totally precise.
68+
*
69+
* @returns Range to pass to {@link Query.matches}
70+
*/
71+
function getQuerySearchRange(
72+
document: TextDocument,
73+
position: Position,
74+
direction: Direction,
75+
{ containment, distalPosition }: ScopeIteratorRequirements,
76+
) {
77+
const offset = document.offsetAt(position);
78+
const distalOffset =
79+
distalPosition == null ? null : document.offsetAt(distalPosition);
80+
81+
if (containment === "required") {
82+
// If containment is required, we smear the position left and right by one
83+
// character so that we have a non-empty intersection with any scope that
84+
// touches position
85+
return {
86+
start: document.positionAt(offset - 1),
87+
end: document.positionAt(offset + 1),
88+
};
89+
}
90+
91+
// If containment is disallowed, we can shift the position forward by a character to avoid
92+
// matching scopes that touch position. Otherwise, we shift the position backward by a
93+
// character to ensure we get scopes that touch position.
94+
const proximalShift = containment === "disallowed" ? 1 : -1;
95+
96+
// FIXME: Don't go all the way to end of document when there is no distalPosition?
97+
// Seems wasteful to query all the way to end of document for something like "next funk"
98+
// Might be better to start smaller and exponentially grow
99+
return direction === "forward"
100+
? {
101+
start: document.positionAt(offset + proximalShift),
102+
end:
103+
distalOffset == null
104+
? document.range.end
105+
: document.positionAt(distalOffset + 1),
106+
}
107+
: {
108+
start:
109+
distalOffset == null
110+
? document.range.start
111+
: document.positionAt(distalOffset - 1),
112+
end: document.positionAt(offset - proximalShift),
113+
};
114+
}
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,62 @@
1-
import {
2-
ScopeType,
3-
SimpleScopeType, TextEditor
4-
} from "@cursorless/common";
1+
import { ScopeType, SimpleScopeType, TextEditor } from "@cursorless/common";
52
import { Query, QueryMatch } from "web-tree-sitter";
63
import { TreeSitter } from "../../../..";
74
import { PlainTarget } from "../../../targets";
85
import { TargetScope } from "../scope.types";
96
import { BaseTreeSitterScopeHandler } from "./BaseTreeSitterScopeHandler";
10-
import { getRelatedRange, getCaptureRangeByName } from "./getCaptureRangeByName";
11-
7+
import { getRelatedRange, getCaptureRangeByName } from "./captureUtils";
128

9+
/** Scope handler to be used for iteration scopes of tree-sitter scope types */
1310
export class TreeSitterIterationScopeHandler extends BaseTreeSitterScopeHandler {
1411
protected isHierarchical = true;
12+
13+
// Doesn't correspond to any scope type
1514
public scopeType = undefined;
1615

16+
// Doesn't have any iteration scope type itself; that would correspond to
17+
// something like "every every"
1718
public get iterationScopeType(): ScopeType {
1819
throw Error("Not implemented");
1920
}
2021

2122
constructor(
2223
treeSitter: TreeSitter,
2324
query: Query,
24-
private iterateeScopeType: SimpleScopeType
25+
/** The scope type for which we are the iteration scope */
26+
private iterateeScopeType: SimpleScopeType,
2527
) {
2628
super(treeSitter, query);
2729
}
2830

2931
protected matchToScope(
3032
editor: TextEditor,
31-
match: QueryMatch
33+
match: QueryMatch,
3234
): TargetScope | undefined {
3335
const scopeTypeType = this.iterateeScopeType.type;
3436

3537
const contentRange = getRelatedRange(match, scopeTypeType, "iteration")!;
3638

3739
if (contentRange == null) {
40+
// This capture was for some unrelated scope type
3841
return undefined;
3942
}
4043

41-
const domain = getCaptureRangeByName(
42-
match,
43-
`${scopeTypeType}.iteration.domain`,
44-
`_.iteration.domain`
45-
) ?? contentRange;
44+
const domain =
45+
getCaptureRangeByName(
46+
match,
47+
`${scopeTypeType}.iteration.domain`,
48+
`_.iteration.domain`,
49+
) ?? contentRange;
4650

4751
return {
4852
editor,
4953
domain,
50-
getTarget: (isReversed) => new PlainTarget({
51-
editor,
52-
isReversed,
53-
contentRange,
54-
}),
54+
getTarget: (isReversed) =>
55+
new PlainTarget({
56+
editor,
57+
isReversed,
58+
contentRange,
59+
}),
5560
};
5661
}
5762
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import { TargetScope } from "../scope.types";
77
import { CustomScopeType } from "../scopeHandler.types";
88
import { BaseTreeSitterScopeHandler } from "./BaseTreeSitterScopeHandler";
99
import { TreeSitterIterationScopeHandler } from "./TreeSitterIterationScopeHandler";
10-
import {
11-
getCaptureRangeByName,
12-
getRelatedRange,
13-
} from "./getCaptureRangeByName";
10+
import { getCaptureRangeByName, getRelatedRange } from "./captureUtils";
1411

1512
/**
1613
* Handles scopes that are implemented using tree-sitter.
@@ -26,6 +23,8 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler {
2623
super(treeSitter, query);
2724
}
2825

26+
// We just create a custom scope handler that doesn't necessarily correspond
27+
// to any well-defined scope type
2928
public get iterationScopeType(): CustomScopeType {
3029
return {
3130
type: "custom",
@@ -46,6 +45,7 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler {
4645
const contentRange = getCaptureRangeByName(match, scopeTypeType);
4746

4847
if (contentRange == null) {
48+
// This capture was for some unrelated scope type
4949
return undefined;
5050
}
5151

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@ import { getNodeRange } from "../../../../util/nodeSelectors";
1515
export function getRelatedRange(
1616
match: QueryMatch,
1717
scopeTypeType: string,
18-
relationship: string
18+
relationship: string,
1919
) {
2020
return getCaptureRangeByName(
2121
match,
2222
`${scopeTypeType}.${relationship}`,
23-
`_.${relationship}`
23+
`_.${relationship}`,
2424
);
2525
}
2626

27+
/**
28+
* Looks in the captures of a match for a capture with one of the given names, and
29+
* returns the range of that capture, or undefined if no matching capture was found
30+
*
31+
* @param match The match to get the range from
32+
* @param names The possible names of the capture to get the range for
33+
* @returns A range or undefined if no matching capture was found
34+
*/
2735
export function getCaptureRangeByName(match: QueryMatch, ...names: string[]) {
28-
const relatedNode = match.captures.find((capture) => names.some((name) => capture.name === name)
36+
const relatedNode = match.captures.find((capture) =>
37+
names.some((name) => capture.name === name),
2938
)?.node;
3039

3140
return relatedNode == null ? undefined : getNodeRange(relatedNode);

0 commit comments

Comments
 (0)