Skip to content

Commit 6534743

Browse files
Text fragment scope (#2381)
Adds text fragment as a hidden scope. Allows us to use scope visualizer and facet scope fixtures. Additional languages can be done in a follow up. This just implements the machinery and a single language as proof of concept. ![Screenshot 2024-06-01T15-42-38 - Visual Studio Code](https://github.com/cursorless-dev/cursorless/assets/3511326/f2283790-d8dc-4c42-b5c0-4ca0783a22dc) ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [/] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [/] I have not broken the cheatsheet --------- Co-authored-by: Pokey Rule <755842+pokey@users.noreply.github.com>
1 parent 820732a commit 6534743

File tree

17 files changed

+98
-94
lines changed

17 files changed

+98
-94
lines changed

cursorless-talon/src/spoken_forms.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ def handle_new_values(csv_name: str, values: list[SpokenFormEntry]):
130130
extra_allowed_values=[
131131
"private.fieldAccess",
132132
"private.switchStatementSubject",
133+
"textFragment",
133134
],
134135
default_list_name="scope_type",
135136
),
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
the
3+
comment
4+
*/
5+
---
6+
7+
[Content] =
8+
[Removal] =
9+
[Domain] = 0:0-3:2
10+
>--
11+
0| /*
12+
1| the
13+
2| comment
14+
3| */
15+
--<
16+
17+
[Insertion delimiter] = "\n"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
the
3+
comment
4+
*/
5+
---
6+
7+
[Content] =
8+
[Removal] =
9+
[Domain] = 0:0-3:2
10+
>--
11+
0| /*
12+
1| the
13+
2| comment
14+
3| */
15+
--<
16+
17+
[Insertion delimiter] = " "
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
int a; // the comment
2+
---
3+
4+
[Content] =
5+
[Domain] = 0:7-0:21
6+
>--------------<
7+
0| int a; // the comment
8+
9+
[Removal] = 0:6-0:21
10+
>---------------<
11+
0| int a; // the comment
12+
13+
[Leading delimiter] = 0:6-0:7
14+
>-<
15+
0| int a; // the comment
16+
17+
[Insertion delimiter] = " "
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
char* a = "hello world";
2+
---
3+
4+
[Content] =
5+
[Removal] =
6+
[Domain] = 0:11-0:22
7+
>-----------<
8+
0| char* a = "hello world";
9+
10+
[Insertion delimiter] = " "

packages/common/src/scopeSupportFacets/c.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ const { supported, unsupported, notApplicable } = ScopeSupportFacetLevel;
1010

1111
export const cScopeSupport: LanguageScopeSupportFacetMap = {
1212
ifStatement: supported,
13+
1314
"comment.line": supported,
15+
"comment.block": supported,
1416
"string.singleLine": supported,
17+
"textFragment.comment.line": supported,
18+
"textFragment.comment.block": supported,
19+
"textFragment.string.singleLine": supported,
1520

1621
class: supported,
1722
className: supported,

packages/common/src/scopeSupportFacets/scopeSupportFacetInfos.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,23 @@ export const scopeSupportFacetInfos: Record<
225225
scopeType: "string",
226226
},
227227

228+
"textFragment.comment.line": {
229+
description: "Text fragment consisting of a line comment",
230+
scopeType: "textFragment",
231+
},
232+
"textFragment.comment.block": {
233+
description: "Text fragment consisting of a block comment",
234+
scopeType: "textFragment",
235+
},
236+
"textFragment.string.singleLine": {
237+
description: "Text fragment consisting of a single-line string",
238+
scopeType: "textFragment",
239+
},
240+
"textFragment.string.multiLine": {
241+
description: "Text fragment consisting of a multi-line string",
242+
scopeType: "textFragment",
243+
},
244+
228245
"branch.if": {
229246
description: "An if/elif/else branch",
230247
scopeType: "branch",

packages/common/src/scopeSupportFacets/scopeSupportFacets.types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ const scopeSupportFacets = [
5757
"string.singleLine",
5858
"string.multiLine",
5959

60+
"textFragment.comment.line",
61+
"textFragment.comment.block",
62+
"textFragment.string.singleLine",
63+
"textFragment.string.multiLine",
64+
6065
"branch.if",
6166
"branch.if.iteration",
6267
"branch.try",
@@ -124,7 +129,6 @@ const scopeSupportFacets = [
124129
// selector
125130
// unit
126131
// collectionItem
127-
// textFragment
128132
] as const;
129133

130134
const textualScopeSupportFacets = [

packages/common/src/types/command/PartialTargetDescriptor.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ export const simpleScopeTypeTypes = [
173173
"notebookCell",
174174
// Talon
175175
"command",
176+
// Private scope types
177+
"textFragment",
176178
] as const;
177179

178180
export function isSimpleScopeType(

packages/cursorless-engine/src/languages/LanguageDefinition.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,11 @@ import {
66
} from "@cursorless/common";
77
import { basename, dirname, join } from "path";
88
import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers";
9-
import { TreeSitterTextFragmentScopeHandler } from "../processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterTextFragmentScopeHandler";
10-
import { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types";
119
import { ide } from "../singletons/ide.singleton";
1210
import { TreeSitter } from "../typings/TreeSitter";
1311
import { matchAll } from "../util/regex";
1412
import { TreeSitterQuery } from "./TreeSitterQuery";
1513
import { validateQueryCaptures } from "./TreeSitterQuery/validateQueryCaptures";
16-
import { TEXT_FRAGMENT_CAPTURE_NAME } from "./captureNames";
1714

1815
/**
1916
* Represents a language definition for a single language, including the
@@ -76,14 +73,6 @@ export class LanguageDefinition {
7673

7774
return new TreeSitterScopeHandler(this.query, scopeType as SimpleScopeType);
7875
}
79-
80-
getTextFragmentScopeHandler(): ScopeHandler | undefined {
81-
if (!this.query.captureNames.includes(TEXT_FRAGMENT_CAPTURE_NAME)) {
82-
return undefined;
83-
}
84-
85-
return new TreeSitterTextFragmentScopeHandler(this.query);
86-
}
8776
}
8877

8978
/**

packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,6 @@ const testCases: { name: string; isOk: boolean; content: string }[] = [
8181
isOk: false,
8282
content: "(if_statement) @statement.leading.start",
8383
},
84-
{
85-
name: "Text fragment removal",
86-
isOk: false,
87-
content: "(comment) @textFragment.removal",
88-
},
8984
];
9085

9186
suite("validateQueryCaptures", function () {

packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { showError, simpleScopeTypeTypes } from "@cursorless/common";
22
import { ide } from "../../singletons/ide.singleton";
33

44
const wildcard = "_";
5-
const textFragment = "textFragment";
65
const captureNames = [wildcard, ...simpleScopeTypeTypes];
76

87
const positionRelationships = ["prefix", "leading", "trailing"];
@@ -26,12 +25,6 @@ const rangeSuffixes = [
2625

2726
const allowedCaptures = new Set<string>();
2827

29-
allowedCaptures.add(textFragment);
30-
31-
for (const suffix of rangeSuffixes) {
32-
allowedCaptures.add(`${textFragment}.${suffix}`);
33-
}
34-
3528
for (const captureName of captureNames) {
3629
// Wildcard is not allowed by itself without a relationship
3730
if (captureName !== wildcard) {

packages/cursorless-engine/src/languages/captureNames.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

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

Lines changed: 0 additions & 62 deletions
This file was deleted.

packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ function processSurroundingPairCore(
102102

103103
const textFragmentRange = (() => {
104104
// First try to use the text fragment scope handler if it exists
105-
const textFragmentScopeHandler =
106-
languageDefinition?.getTextFragmentScopeHandler();
105+
const textFragmentScopeHandler = languageDefinition?.getScopeHandler({
106+
type: "textFragment",
107+
});
107108

108109
if (textFragmentScopeHandler != null) {
109110
const containingScope = getContainingScopeTarget(
@@ -115,6 +116,7 @@ function processSurroundingPairCore(
115116
return containingScope?.[0].contentRange;
116117
}
117118

119+
// If we don't find a text fragment we fall back to the full document range
118120
return document.range;
119121
})();
120122

packages/cursorless-engine/src/scopeProviders/ScopeInfoProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ function isLanguageSpecific(scopeType: ScopeType): boolean {
157157
case "namedParagraph":
158158
case "subParagraph":
159159
case "environment":
160+
case "textFragment":
160161
return true;
161162

162163
case "character":

packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = {
9696
url: "link",
9797
notebookCell: "cell",
9898

99-
["private.fieldAccess"]: isPrivate("access"),
10099
string: isPrivate("parse tree string"),
100+
textFragment: isPrivate("text fragment"),
101+
["private.fieldAccess"]: isPrivate("access"),
101102
["private.switchStatementSubject"]: isPrivate("subject"),
102103
},
103104
complexScopeTypeType: {

0 commit comments

Comments
 (0)