Skip to content

Commit 22129f2

Browse files
Use narrow search range instead of full document for Tree sitter queries
1 parent 43e9e21 commit 22129f2

File tree

5 files changed

+110
-16
lines changed

5 files changed

+110
-16
lines changed

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import { showError } from "@cursorless/common";
33
import { uniqWith } from "lodash-es";
44
import type { TreeSitterQuery } from "../../../../languages/TreeSitterQuery";
55
import type { QueryMatch } from "../../../../languages/TreeSitterQuery/QueryCapture";
6+
import { ide } from "../../../../singletons/ide.singleton";
67
import { BaseScopeHandler } from "../BaseScopeHandler";
78
import { compareTargetScopes } from "../compareTargetScopes";
89
import type { TargetScope } from "../scope.types";
910
import type { ScopeIteratorRequirements } from "../scopeHandler.types";
11+
import { getQuerySearchRange } from "./getQuerySearchRange";
1012
import { mergeAdjacentBy } from "./mergeAdjacentBy";
11-
import { ide } from "../../../../singletons/ide.singleton";
1213

1314
/** Base scope handler to use for both tree-sitter scopes and their iteration scopes */
1415
export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler {
@@ -20,18 +21,20 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler {
2021
editor: TextEditor,
2122
position: Position,
2223
direction: Direction,
23-
_hints: ScopeIteratorRequirements,
24+
hints: ScopeIteratorRequirements,
2425
): Iterable<TargetScope> {
2526
const { document } = editor;
2627

27-
// Due to a tree-sitter bug, we generate all scopes from the entire file
28-
// instead of using `_hints` to restrict the search range to scopes we care
29-
// about. The actual scopes yielded to the client are filtered by
30-
// `BaseScopeHandler` anyway, so there's no impact on correctness, just
31-
// performance. We'd like to roll this back; see #1769.
28+
/** Narrow the range within which tree-sitter searches, for performance */
29+
const { start, end } = getQuerySearchRange(
30+
document,
31+
position,
32+
direction,
33+
hints,
34+
);
3235

3336
const scopes = this.query
34-
.matches(document)
37+
.matches(document, start, end)
3538
.map((match) => this.matchToScope(editor, match))
3639
.filter((scope): scope is ExtendedTargetScope => scope != null)
3740
.sort((a, b) => compareTargetScopes(direction, position, a, b));
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { Direction, Position, TextDocument } from "@cursorless/common";
2+
import type {
3+
ContainmentPolicy,
4+
ScopeIteratorRequirements,
5+
} from "../scopeHandler.types";
6+
7+
/**
8+
* Constructs a range to pass to {@link Query.matches} to find scopes. Note
9+
* that {@link Query.matches} will only return scopes that have non-empty
10+
* intersection with this range. Also note that the base
11+
* {@link BaseScopeHandler.generateScopes} will filter out any extra scopes
12+
* that we yield, so we don't need to be totally precise.
13+
*
14+
* @returns Range to pass to {@link Query.matches}
15+
*/
16+
export function getQuerySearchRange(
17+
document: TextDocument,
18+
position: Position,
19+
direction: Direction,
20+
{
21+
containment,
22+
distalPosition,
23+
allowAdjacentScopes,
24+
}: ScopeIteratorRequirements,
25+
) {
26+
const { start, end } = getQuerySearchRangeCore(
27+
document.offsetAt(position),
28+
document.offsetAt(distalPosition),
29+
direction,
30+
containment,
31+
allowAdjacentScopes,
32+
);
33+
34+
return {
35+
start: document.positionAt(start),
36+
end: document.positionAt(end),
37+
};
38+
}
39+
40+
/** Helper function for {@link getQuerySearchRange} */
41+
function getQuerySearchRangeCore(
42+
offset: number,
43+
distalOffset: number,
44+
direction: Direction,
45+
containment: ContainmentPolicy | null,
46+
allowAdjacentScopes: boolean,
47+
) {
48+
/**
49+
* If we allow adjacent scopes, we need to shift some of our offsets by one
50+
* character
51+
*/
52+
const adjacentShift = allowAdjacentScopes ? 1 : 0;
53+
54+
if (containment === "required") {
55+
// If containment is required, we smear the position left and right by one
56+
// character so that we have a non-empty intersection with any scope that
57+
// touches position. Note that we can skip shifting the initial position
58+
// if we disallow adjacent scopes.
59+
return direction === "forward"
60+
? {
61+
start: offset - adjacentShift,
62+
end: offset + 1,
63+
}
64+
: {
65+
start: offset - 1,
66+
end: offset + adjacentShift,
67+
};
68+
}
69+
70+
// If containment is disallowed, we can shift the position forward by a
71+
// character to avoid matching scopes that touch position. Otherwise, we
72+
// shift the position backward by a character to ensure we get scopes that
73+
// touch position, if we allow adjacent scopes.
74+
const proximalShift = containment === "disallowed" ? 1 : -adjacentShift;
75+
76+
// FIXME: Don't go all the way to end of document when there is no
77+
// distalPosition? Seems wasteful to query all the way to end of document for
78+
// something like "next funk" Might be better to start smaller and
79+
// exponentially grow
80+
return direction === "forward"
81+
? {
82+
start: offset + proximalShift,
83+
end: distalOffset + adjacentShift,
84+
}
85+
: {
86+
start: distalOffset - adjacentShift,
87+
end: offset - proximalShift,
88+
};
89+
}

queries/c.scm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@
182182
) @_.domain
183183

184184
;;!! aaa = 0;
185-
(
185+
(_
186186
(assignment_expression
187187
left: (_) @name @value.leading.endOf
188188
right: (_) @value @name.trailing.startOf

queries/javascript.scm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
;; Define this here because the `field_definition` node type doesn't exist
88
;; in typescript.
9-
(
9+
(_
1010
;;!! class Foo {
1111
;;!! foo = () => {};
1212
;;! ^^^^^^^^^^^^^^^
@@ -31,7 +31,7 @@
3131
";"? @namedFunction.end @functionName.domain.end
3232
)
3333

34-
(
34+
(_
3535
;;!! foo = ...;
3636
;;! ^^^-------
3737
(field_definition

queries/typescript.core.scm

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
) @_.domain
4040

4141
;; Define these here because these node types don't exist in javascript.
42-
(
42+
(_
4343
[
4444
;;!! class Foo { foo() {} }
4545
;;! ^^^^^^^^
@@ -80,7 +80,7 @@
8080
";"? @namedFunction.end @functionName.domain.end @name.domain.end
8181
)
8282

83-
(
83+
(_
8484
;;!! (public | private | protected) foo = ...;
8585
;;! -----------------------------------------
8686
(public_field_definition
@@ -92,7 +92,7 @@
9292
";"? @_.domain.end
9393
)
9494

95-
(
95+
(_
9696
;;!! (public | private | protected) foo: Bar = ...;
9797
;;! ----------------------------------------------
9898
(public_field_definition
@@ -340,14 +340,15 @@
340340
;;! ^^^^
341341
;;! xxxxxx
342342
;;! ------------
343-
(
343+
(_
344344
(property_signature
345345
name: (_) @collectionKey @type.leading.endOf
346346
type: (_
347347
":"
348348
(_) @type @collectionKey.trailing.startOf
349349
)
350350
) @_.domain.start
351+
.
351352
";"? @_.domain.end
352353
)
353354

@@ -372,11 +373,12 @@
372373
)
373374

374375
;; Statements with optional trailing `;`
375-
(
376+
(_
376377
[
377378
(property_signature)
378379
(public_field_definition)
379380
(abstract_method_signature)
380381
] @statement.start
382+
.
381383
";"? @statement.end
382384
)

0 commit comments

Comments
 (0)