Skip to content

Commit a54fb0e

Browse files
committed
Added its modifier to use with inference
1 parent 29d5849 commit a54fb0e

24 files changed

+425
-212
lines changed

cursorless-talon/src/modifiers/modifiers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"trailing": "trailing",
2020
"content": "keepContentFilter",
2121
"empty": "keepEmptyFilter",
22+
"its": "inferPreviousMark",
2223
}
2324

2425
mod.list(

docs/user/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ For example:
333333

334334
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.
335335

336+
#### `"its"`
337+
338+
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.
339+
340+
For example, `"take air past end of its line"` selects the range from the token containing letter `a` to the end of the line containing the same token. This is in contrast from `"take air past end of line"` which selects the range from the token containing letter `a` to the end of the line containing the current selection.
341+
342+
Another example is `"bring air to its value"`, which would cause the token with a hat over `a` to replace the return value containing it.
343+
336344
### Compound targets
337345

338346
Individual targets can be combined into compound targets to make bigger targets or refer to multiple selections at the same time.

src/core/commandRunner/CommandRunner.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import {
1010
SelectionWithEditor,
1111
} from "../../typings/Types";
1212
import { isString } from "../../util/type";
13-
import { canonicalizeAndValidateCommand } from "../commandVersionUpgrades/canonicalizeAndValidateCommand";
13+
import {
14+
canonicalizeAndValidateCommand,
15+
checkForOldInference,
16+
} from "../commandVersionUpgrades/canonicalizeAndValidateCommand";
1417
import { PartialTargetV0V1 } from "../commandVersionUpgrades/upgradeV1ToV2/commandV1.types";
1518
import inferFullTargets from "../inferFullTargets";
1619
import { ThatMark } from "../ThatMark";
@@ -135,6 +138,10 @@ export default class CommandRunner {
135138
);
136139
}
137140

141+
// NB: We do this once test recording has started so that we can capture
142+
// warning.
143+
checkForOldInference(this.graph, partialTargetDescriptors);
144+
138145
const targets = processTargets(
139146
processedTargetsContext,
140147
targetDescriptors

src/core/commandVersionUpgrades/canonicalizeAndValidateCommand.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { window } from "vscode";
12
import { ActionType } from "../../actions/actions.types";
23
import { OutdatedExtensionError } from "../../errors";
34
import {
45
Modifier,
56
PartialTargetDescriptor,
67
SimpleScopeTypeType,
78
} from "../../typings/targetDescriptor.types";
9+
import { Graph } from "../../typings/Types";
810
import { getPartialPrimitiveTargets } from "../../util/getPrimitiveTargets";
11+
import { globalStateKeys } from "../../util/globalStateKeys";
912
import {
1013
Command,
1114
CommandComplete,
@@ -84,7 +87,7 @@ function upgradeCommand(command: Command): CommandLatest {
8487
return command;
8588
}
8689

87-
export function validateCommand(
90+
function validateCommand(
8891
actionName: ActionType,
8992
partialTargets: PartialTargetDescriptor[]
9093
) {
@@ -110,3 +113,40 @@ function usesScopeType(
110113
)
111114
);
112115
}
116+
117+
export async function checkForOldInference(
118+
graph: Graph,
119+
partialTargets: PartialTargetDescriptor[]
120+
) {
121+
const hasOldInference = partialTargets.some((target) => {
122+
return (
123+
target.type === "range" &&
124+
target.active.mark == null &&
125+
target.active.modifiers?.some((m) => m.type === "position") &&
126+
!target.active.modifiers?.some((m) => m.type === "inferPreviousMark")
127+
);
128+
});
129+
130+
if (hasOldInference) {
131+
const hideInferenceWarning =
132+
graph.extensionContext.globalState.get<boolean>(
133+
globalStateKeys.hideInferenceWarning,
134+
false
135+
);
136+
137+
if (!hideInferenceWarning) {
138+
const pressed = await graph.ide.messages.showWarning(
139+
"deprecatedPositionInference",
140+
'The "past start of" / "past end of" form has changed behavior. For the old behavior, you can now say "past start of its" / "past end of its". For example, "take air past end of its line". You may also consider using "head" / "tail" instead; see https://www.cursorless.org/docs/#head-and-tail',
141+
"Don't show again"
142+
);
143+
144+
if (pressed) {
145+
graph.extensionContext.globalState.update(
146+
globalStateKeys.hideInferenceWarning,
147+
true
148+
);
149+
}
150+
}
151+
}
152+
}

src/core/inferFullTargets.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,17 @@ function inferPrimitiveTarget(
9898
}
9999

100100
const ownPositionModifier = getPositionModifier(target);
101-
const ownNonPositionModifiers = getNonPositionModifiers(target);
101+
const ownModifiers = getPreservedModifiers(target);
102102

103-
// Position without a mark can be something like "take air past end of line"
104-
// We will remove this case when we implement #736
105103
const mark = target.mark ??
106-
(ownPositionModifier == null ? null : getPreviousMark(previousTargets)) ?? {
104+
(shouldInferPreviousMark(target)
105+
? getPreviousMark(previousTargets)
106+
: null) ?? {
107107
type: "cursor",
108108
};
109109

110110
const modifiers =
111-
ownNonPositionModifiers ??
112-
getPreviousNonPositionModifiers(previousTargets) ??
113-
[];
111+
ownModifiers ?? getPreviousPreservedModifiers(previousTargets) ?? [];
114112

115113
const positionModifier =
116114
ownPositionModifier ?? getPreviousPositionModifier(previousTargets);
@@ -143,22 +141,33 @@ function getPositionModifier(
143141
: (target.modifiers[positionModifierIndex] as PositionModifier);
144142
}
145143

144+
function shouldInferPreviousMark(
145+
target: PartialPrimitiveTargetDescriptor
146+
): boolean {
147+
return target.modifiers?.some((m) => m.type === "inferPreviousMark") ?? false;
148+
}
149+
146150
/**
147-
* Return a list of non-positional modifiers on the given target. We return
148-
* undefined if there are none. Note that we will never return an empty list; we
149-
* will always return `undefined` if there are no non-positional modifiers.
150-
* @param target The target from which to get the non-positional modifiers
151-
* @returns A list of non-positional modifiers or `undefined` if there are none
151+
* Return a list of modifiers that should not be removed during inference.
152+
* Today, we remove positional modifiers, because they have their own field on
153+
* the full targets. We also remove modifiers that only impact inference, such
154+
* as `inferPreviousMark`.
155+
*
156+
* We return `undefined` if there are no preserved modifiers. Note that we will
157+
* never return an empty list; we will always return `undefined` if there are no
158+
* preserved modifiers.
159+
* @param target The target from which to get the modifiers
160+
* @returns A list of preserved modifiers or `undefined` if there are none
152161
*/
153-
function getNonPositionModifiers(
162+
function getPreservedModifiers(
154163
target: PartialPrimitiveTargetDescriptor
155164
): Modifier[] | undefined {
156-
const nonPositionModifiers = target.modifiers?.filter(
157-
(modifier) => modifier.type !== "position"
165+
const preservedModifiers = target.modifiers?.filter(
166+
(modifier) => !["position", "inferPreviousMark"].includes(modifier.type)
158167
);
159-
return nonPositionModifiers == null || nonPositionModifiers.length === 0
168+
return preservedModifiers == null || preservedModifiers.length === 0
160169
? undefined
161-
: nonPositionModifiers;
170+
: preservedModifiers;
162171
}
163172

164173
function getPreviousMark(
@@ -170,10 +179,10 @@ function getPreviousMark(
170179
);
171180
}
172181

173-
function getPreviousNonPositionModifiers(
182+
function getPreviousPreservedModifiers(
174183
previousTargets: PartialTargetDescriptor[]
175184
): Modifier[] | undefined {
176-
return getPreviousTargetAttribute(previousTargets, getNonPositionModifiers);
185+
return getPreviousTargetAttribute(previousTargets, getPreservedModifiers);
177186
}
178187

179188
function getPreviousPositionModifier(

src/extension.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ThatMark } from "./core/ThatMark";
44
import isTesting from "./testUtil/isTesting";
55
import { Graph } from "./typings/Types";
66
import { getCommandServerApi, getParseTreeApi } from "./util/getExtensionApi";
7+
import { globalStateKeys } from "./util/globalStateKeys";
78
import graphFactories from "./util/graphFactories";
89
import makeGraph, { FactoryMap } from "./util/makeGraph";
910

@@ -36,6 +37,11 @@ export async function activate(context: vscode.ExtensionContext) {
3637
graph.cheatsheet.init();
3738
graph.statusBarItem.init();
3839

40+
// Mark these keys for synchronization
41+
graph.extensionContext.globalState.setKeysForSync([
42+
globalStateKeys.hideInferenceWarning,
43+
]);
44+
3945
const thatMark = new ThatMark();
4046
const sourceMark = new ThatMark();
4147

src/processTargets/getModifierStage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ export default (modifier: Modifier): ModifierStage => {
7979
return new ModifyIfUntypedStage(modifier);
8080
case "range":
8181
return new RangeModifierStage(modifier);
82+
case "inferPreviousMark":
83+
throw Error(
84+
`Unexpected modifier '${modifier.type}'; it should have been removed during inference`
85+
);
8286
}
8387
};
8488

src/test/suite/fixtures/recorded/compoundTargets/takePastEndOfToken.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ finalState:
2121
- anchor: {line: 0, character: 8}
2222
active: {line: 0, character: 11}
2323
fullTargets: [{type: range, excludeStart: false, excludeEnd: false, start: {type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: identity}, insideOutsideType: inside}, end: {type: primitive, mark: {type: cursorToken}, selectionType: token, position: after, modifier: {type: identity}, insideOutsideType: inside}}]
24+
ide:
25+
messages:
26+
- {type: warning, id: deprecatedPositionInference}

src/test/suite/fixtures/recorded/compoundTargets/takePastStartOfToken.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ finalState:
2121
- anchor: {line: 0, character: 8}
2222
active: {line: 0, character: 6}
2323
fullTargets: [{type: range, excludeStart: false, excludeEnd: false, start: {type: primitive, mark: {type: cursor}, selectionType: token, position: contents, modifier: {type: identity}, insideOutsideType: inside}, end: {type: primitive, mark: {type: cursorToken}, selectionType: token, position: before, modifier: {type: identity}, insideOutsideType: inside}}]
24+
ide:
25+
messages:
26+
- {type: warning, id: deprecatedPositionInference}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: bring batt before funk
4+
version: 3
5+
targets:
6+
- type: primitive
7+
mark: {type: decoratedSymbol, symbolColor: default, character: b}
8+
- type: primitive
9+
modifiers:
10+
- {type: position, position: before}
11+
- type: containingScope
12+
scopeType: {type: namedFunction}
13+
usePrePhraseSnapshot: true
14+
action: {name: replaceWithTarget}
15+
initialState:
16+
documentContents: |-
17+
function foo() {
18+
return "";
19+
}
20+
21+
function bar() {
22+
return "";
23+
}
24+
selections:
25+
- anchor: {line: 1, character: 12}
26+
active: {line: 1, character: 12}
27+
marks:
28+
default.b:
29+
start: {line: 4, character: 9}
30+
end: {line: 4, character: 12}
31+
finalState:
32+
documentContents: |-
33+
bar
34+
35+
function foo() {
36+
return "";
37+
}
38+
39+
function bar() {
40+
return "";
41+
}
42+
selections:
43+
- anchor: {line: 3, character: 12}
44+
active: {line: 3, character: 12}
45+
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b}, modifiers: []}, {type: primitive, mark: {type: cursor}, modifiers: [{type: containingScope, scopeType: {type: namedFunction}}], positionModifier: {type: position, position: before}}]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: bring batt before its funk
4+
version: 3
5+
targets:
6+
- type: primitive
7+
mark: {type: decoratedSymbol, symbolColor: default, character: b}
8+
- type: primitive
9+
modifiers:
10+
- {type: position, position: before}
11+
- {type: inferPreviousMark}
12+
- type: containingScope
13+
scopeType: {type: namedFunction}
14+
usePrePhraseSnapshot: true
15+
action: {name: replaceWithTarget}
16+
initialState:
17+
documentContents: |-
18+
function foo() {
19+
return "";
20+
}
21+
22+
function bar() {
23+
return "";
24+
}
25+
selections:
26+
- anchor: {line: 1, character: 12}
27+
active: {line: 1, character: 12}
28+
marks:
29+
default.b:
30+
start: {line: 4, character: 9}
31+
end: {line: 4, character: 12}
32+
finalState:
33+
documentContents: |-
34+
function foo() {
35+
return "";
36+
}
37+
38+
bar
39+
40+
function bar() {
41+
return "";
42+
}
43+
selections:
44+
- anchor: {line: 1, character: 12}
45+
active: {line: 1, character: 12}
46+
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b}, modifiers: []}, {type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: b}, modifiers: [{type: containingScope, scopeType: {type: namedFunction}}], positionModifier: {type: position, position: before}}]

src/test/suite/fixtures/recorded/inference/bringLineBatPastEndOfFunkToThis.yml

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

0 commit comments

Comments
 (0)