Skip to content

Commit 837db66

Browse files
committed
Handle words
1 parent 9c4ee17 commit 837db66

File tree

6 files changed

+74
-1
lines changed

6 files changed

+74
-1
lines changed

docs/user/README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,26 @@ foo.bar baz|bongo
311311

312312
Saying `"every paint"` would select `foo.bar` and `baz|bongo`.
313313

314+
##### `"instance"`
315+
316+
The `"instance"` modifier searches for occurrences of the text of the target. For example:
317+
318+
- `"take every instance air"`: selects all instances of the token with a hat over the letter `a` in the whole document
319+
- `"take two instances air"`: selects the first two instances of the token with a hat over the letter `a`, starting from that token itself
320+
- `"take next instance air"`: selects the next instance of the token with a hat over the letter `a`, starting from that token itself
321+
- `"chuck every instance two tokens air"`: deletes all occurrences of the two tokens beginning from the token with a hat over the letter `a`. For example if there were a hat over the `a` in `aaa.bbb`, it would delete every occurrence of `aaa.` in the file.
322+
- `"take every instance air past bat"`: if there were hats over the `a` and `b` in `aaa ccc bbb ddd`, it would selects all occurrences of `aaa ccc bbb` (which is the text corresponding to the range `"air past bat"`)
323+
324+
Note in the final example how the `"instance"` modifier constructs the instance based on the range `"air past bat"`, rather than the individual tokens `"air"` and `"bat"`, as you might expect given the way other modifiers behave. Effectively `"instance"` applies to everything after the `"instance"` modifier, rather than just the next modifier.
325+
326+
Note also that `"instance"` considers the type of target used to construct the instance. So for example, `"take every instance air"` will only select tokens that
327+
are identical to the token with a hat over the letter `a`, skipping over bigger tokens that contain the token with a hat over the letter `a` as a substring. For example, if there were a hat over the `a` in `aaa`, it would select every occurrence of `aaa` in the file, but not `aaaaa`. If you want to avoid this behaviour, you can
328+
use the `"just"` modifier, eg `"take every instance just air"`.
329+
330+
If your cursor is touching a token, you can say `"take every instance air"` to select all instances of the given token.
331+
332+
Pro tip: if you say eg `"take five instances air"`, and it turns out you need more, you can say eg `"take that and next two instances that"` to select the next two instances after the last instance you selected.
333+
314334
##### Surrounding pair
315335

316336
Cursorless has support for expanding the selection to the nearest containing paired delimiter, eg the surrounding parentheses, curly brackets, etc.
@@ -351,7 +371,7 @@ For example:
351371

352372
If your cursor / mark is between two delimiters (not adjacent to one), then saying either "left" or "right" will cause cursorless to just expand to the nearest delimiters on either side, without trying to determine whether they are opening or closing delimiters.
353373

354-
#### `"its"`
374+
##### `"its"`
355375

356376
The the modifier `"its"` is intended to be used as part of a compound target, and will tell Cursorless to use the previously mentioned mark in the compound target.
357377

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ export default class InstanceStage implements ModifierStage {
121121
const filterScopeType = getFilterScopeType(target);
122122

123123
if (filterScopeType != null) {
124+
// If the target is a line, token or word, we want to filter out any
125+
// instances of the text that are not also a line, token or word. For
126+
// those that are, we want to return the target as a line, token or word
127+
// target. For example, if the user says "take every instance air", we
128+
// won't return matches where we find the text of the "air" token as part
129+
// of a larger token.
124130
const containingScopeModifier = this.modifierStageFactory.create({
125131
type: "containingScope",
126132
scopeType: filterScopeType,
@@ -129,6 +135,9 @@ export default class InstanceStage implements ModifierStage {
129135
return ifilter(
130136
imap(iterable, (target) => {
131137
try {
138+
// Just try to expand to the containing scope. If it fails or is not
139+
// equal to the target, we know the match is not a line, token or
140+
// word.
132141
const containingScope = containingScopeModifier.run(target);
133142

134143
if (
@@ -160,6 +169,10 @@ function getFilterScopeType(target: Target): ScopeType | null {
160169
return { type: "token" };
161170
}
162171

172+
if (target.isWord) {
173+
return { type: "word" };
174+
}
175+
163176
return null;
164177
}
165178

packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default abstract class BaseTarget implements Target {
3737
isRaw = false;
3838
isImplicit = false;
3939
isNotebookCell = false;
40+
isWord = false;
4041

4142
constructor(parameters: CommonTargetParameters) {
4243
this.state = {

packages/cursorless-engine/src/processTargets/targets/SubTokenWordTarget.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default class SubTokenWordTarget extends BaseTarget {
1414
private trailingDelimiterRange_?: Range;
1515
insertionDelimiter: string;
1616
isToken = false;
17+
isWord = true;
1718

1819
constructor(parameters: SubTokenTargetParameters) {
1920
super(parameters);

packages/cursorless-engine/src/typings/target.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ export interface Target {
4747
/** If true this target should be treated as a token */
4848
readonly isToken: boolean;
4949

50+
/** If true this target should be treated as a word */
51+
readonly isWord: boolean;
52+
5053
/**
5154
* If `true`, then this target has an explicit scope type, and so should never
5255
* be automatically expanded to a containing scope.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: clear two instances last word air
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: relativeScope
10+
scopeType: {type: instance}
11+
offset: 0
12+
length: 2
13+
direction: forward
14+
- type: ordinalScope
15+
scopeType: {type: word}
16+
start: -1
17+
length: 1
18+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
19+
usePrePhraseSnapshot: true
20+
initialState:
21+
documentContents: aaaBbb cccBbbb dddBbb
22+
selections:
23+
- anchor: {line: 0, character: 21}
24+
active: {line: 0, character: 21}
25+
marks:
26+
default.a:
27+
start: {line: 0, character: 0}
28+
end: {line: 0, character: 6}
29+
finalState:
30+
documentContents: aaa cccBbbb ddd
31+
selections:
32+
- anchor: {line: 0, character: 3}
33+
active: {line: 0, character: 3}
34+
- anchor: {line: 0, character: 15}
35+
active: {line: 0, character: 15}

0 commit comments

Comments
 (0)