Skip to content

Commit 9f87f79

Browse files
Changed expansion behavior for bring source targets to closedClosed (#1576)
Fixes #476 ## 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 <[email protected]>
1 parent 79c3358 commit 9f87f79

File tree

5 files changed

+234
-19
lines changed

5 files changed

+234
-19
lines changed

packages/cursorless-engine/src/actions/BringMoveSwap.ts

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,29 @@ class BringMoveSwap implements Action {
171171
? edits
172172
: edits.filter(({ isSource }) => !isSource);
173173

174-
const editSelectionInfos = edits.map(
174+
const sourceEdits =
175+
this.type === "swap"
176+
? []
177+
: edits.filter(({ isSource }) => isSource);
178+
const destinationEdits =
179+
this.type === "swap"
180+
? edits
181+
: edits.filter(({ isSource }) => !isSource);
182+
183+
// Sources should be closedClosed, because they should be logically
184+
// the same as the original source.
185+
const sourceEditSelectionInfos = sourceEdits.map(
186+
({ edit: { range }, originalTarget }) =>
187+
getSelectionInfo(
188+
editor.document,
189+
range.toSelection(originalTarget.isReversed),
190+
RangeExpansionBehavior.closedClosed,
191+
),
192+
);
193+
194+
// Destinations should be openOpen, because they should grow to contain
195+
// the new text.
196+
const destinationEditSelectionInfos = destinationEdits.map(
175197
({ edit: { range }, originalTarget }) =>
176198
getSelectionInfo(
177199
editor.document,
@@ -190,35 +212,64 @@ class BringMoveSwap implements Action {
190212

191213
const editableEditor = ide().getEditableTextEditor(editor);
192214

193-
const [updatedEditSelections, cursorSelections]: Selection[][] =
194-
await performEditsAndUpdateFullSelectionInfos(
195-
this.rangeUpdater,
196-
editableEditor,
197-
filteredEdits.map(({ edit }) => edit),
198-
[editSelectionInfos, cursorSelectionInfos],
199-
);
215+
const [
216+
updatedSourceEditSelections,
217+
updatedDestinationEditSelections,
218+
cursorSelections,
219+
]: Selection[][] = await performEditsAndUpdateFullSelectionInfos(
220+
this.rangeUpdater,
221+
editableEditor,
222+
filteredEdits.map(({ edit }) => edit),
223+
[
224+
sourceEditSelectionInfos,
225+
destinationEditSelectionInfos,
226+
cursorSelectionInfos,
227+
],
228+
);
200229

201230
// NB: We set the selections here because we don't trust vscode to
202231
// properly move the cursor on a bring. Sometimes it will smear an
203232
// empty selection
204233
setSelectionsWithoutFocusingEditor(editableEditor, cursorSelections);
205234

206-
return edits.map((edit, index): MarkEntry => {
207-
const selection = updatedEditSelections[index];
208-
const range = edit.edit.updateRange(selection);
209-
const target = edit.originalTarget;
210-
return {
211-
editor,
212-
selection: range.toSelection(target.isReversed),
213-
isSource: edit.isSource,
214-
target,
215-
};
216-
});
235+
const marks = [
236+
...this.getMarks(sourceEdits, updatedSourceEditSelections),
237+
...this.getMarks(
238+
destinationEdits,
239+
updatedDestinationEditSelections,
240+
),
241+
];
242+
243+
// Restore original order before split into source and destination
244+
marks.sort(
245+
(a, b) =>
246+
edits.findIndex((e) => e.originalTarget === a.target) -
247+
edits.findIndex((e) => e.originalTarget === b.target),
248+
);
249+
250+
return marks;
217251
},
218252
),
219253
);
220254
}
221255

256+
private getMarks(
257+
edits: ExtendedEdit[],
258+
selections: Selection[],
259+
): MarkEntry[] {
260+
return edits.map((edit, index): MarkEntry => {
261+
const selection = selections[index];
262+
const range = edit.edit.updateRange(selection);
263+
const target = edit.originalTarget;
264+
return {
265+
editor: edit.editor,
266+
selection: range.toSelection(target.isReversed),
267+
isSource: edit.isSource,
268+
target,
269+
};
270+
});
271+
}
272+
222273
private async decorateThatMark(thatMark: MarkEntry[]) {
223274
const decorationContext = this.getDecorationContext();
224275
const getRange = (target: Target) =>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: bring air after air
5+
action: {name: replaceWithTarget}
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
9+
- type: primitive
10+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
11+
modifiers:
12+
- {type: position, position: after}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: a
16+
selections:
17+
- anchor: {line: 0, character: 1}
18+
active: {line: 0, character: 1}
19+
marks:
20+
default.a:
21+
start: {line: 0, character: 0}
22+
end: {line: 0, character: 1}
23+
finalState:
24+
documentContents: a a
25+
selections:
26+
- anchor: {line: 0, character: 1}
27+
active: {line: 0, character: 1}
28+
thatMark:
29+
- type: UntypedTarget
30+
contentRange:
31+
start: {line: 0, character: 2}
32+
end: {line: 0, character: 3}
33+
isReversed: false
34+
hasExplicitRange: true
35+
sourceMark:
36+
- type: UntypedTarget
37+
contentRange:
38+
start: {line: 0, character: 0}
39+
end: {line: 0, character: 1}
40+
isReversed: false
41+
hasExplicitRange: true
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: bring air before air
5+
action: {name: replaceWithTarget}
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
9+
- type: primitive
10+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
11+
modifiers:
12+
- {type: position, position: before}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: a
16+
selections:
17+
- anchor: {line: 0, character: 1}
18+
active: {line: 0, character: 1}
19+
marks:
20+
default.a:
21+
start: {line: 0, character: 0}
22+
end: {line: 0, character: 1}
23+
finalState:
24+
documentContents: a a
25+
selections:
26+
- anchor: {line: 0, character: 3}
27+
active: {line: 0, character: 3}
28+
thatMark:
29+
- type: UntypedTarget
30+
contentRange:
31+
start: {line: 0, character: 0}
32+
end: {line: 0, character: 1}
33+
isReversed: false
34+
hasExplicitRange: true
35+
sourceMark:
36+
- type: UntypedTarget
37+
contentRange:
38+
start: {line: 0, character: 2}
39+
end: {line: 0, character: 3}
40+
isReversed: false
41+
hasExplicitRange: true
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: bring air to end of air
5+
action: {name: replaceWithTarget}
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
9+
- type: primitive
10+
modifiers:
11+
- {type: position, position: end}
12+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: a
16+
selections:
17+
- anchor: {line: 0, character: 1}
18+
active: {line: 0, character: 1}
19+
marks:
20+
default.a:
21+
start: {line: 0, character: 0}
22+
end: {line: 0, character: 1}
23+
finalState:
24+
documentContents: aa
25+
selections:
26+
- anchor: {line: 0, character: 2}
27+
active: {line: 0, character: 2}
28+
thatMark:
29+
- type: UntypedTarget
30+
contentRange:
31+
start: {line: 0, character: 1}
32+
end: {line: 0, character: 2}
33+
isReversed: false
34+
hasExplicitRange: true
35+
sourceMark:
36+
- type: UntypedTarget
37+
contentRange:
38+
start: {line: 0, character: 0}
39+
end: {line: 0, character: 1}
40+
isReversed: false
41+
hasExplicitRange: true
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: bring air to start of air
5+
action: {name: replaceWithTarget}
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
9+
- type: primitive
10+
modifiers:
11+
- {type: position, position: start}
12+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: a
16+
selections:
17+
- anchor: {line: 0, character: 1}
18+
active: {line: 0, character: 1}
19+
marks:
20+
default.a:
21+
start: {line: 0, character: 0}
22+
end: {line: 0, character: 1}
23+
finalState:
24+
documentContents: aa
25+
selections:
26+
- anchor: {line: 0, character: 2}
27+
active: {line: 0, character: 2}
28+
thatMark:
29+
- type: UntypedTarget
30+
contentRange:
31+
start: {line: 0, character: 0}
32+
end: {line: 0, character: 1}
33+
isReversed: false
34+
hasExplicitRange: true
35+
sourceMark:
36+
- type: UntypedTarget
37+
contentRange:
38+
start: {line: 0, character: 1}
39+
end: {line: 0, character: 2}
40+
isReversed: false
41+
hasExplicitRange: true

0 commit comments

Comments
 (0)