Skip to content

Commit 933db4a

Browse files
committed
Support delimiter insertion for "paste after"
1 parent 738fe87 commit 933db4a

File tree

9 files changed

+258
-15
lines changed

9 files changed

+258
-15
lines changed

src/actions/Actions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
CopyContentAfter as InsertCopyAfter,
99
CopyContentBefore as InsertCopyBefore,
1010
} from "./InsertCopy";
11-
import { Copy, Cut, Paste } from "./CutCopyPaste";
11+
import { Copy, Cut } from "./CutCopy";
12+
import { Paste } from "./Paste";
1213
import Deselect from "./Deselect";
1314
import { EditNewBefore, EditNewAfter } from "./EditNew";
1415
import ExecuteCommand from "./ExecuteCommand";

src/actions/CutCopyPaste.ts renamed to src/actions/CutCopy.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,3 @@ export class Copy extends CommandAction {
6060
});
6161
}
6262
}
63-
64-
export class Paste extends CommandAction {
65-
constructor(graph: Graph) {
66-
super(graph, {
67-
command: "editor.action.clipboardPasteAction",
68-
ensureSingleEditor: true,
69-
showDecorations: true,
70-
});
71-
}
72-
}

src/actions/Paste.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { commands, DecorationRangeBehavior, window } from "vscode";
2+
import {
3+
callFunctionAndUpdateSelections,
4+
callFunctionAndUpdateSelectionsWithBehavior,
5+
} from "../core/updateSelections/updateSelections";
6+
import { Target } from "../typings/target.types";
7+
import { Graph } from "../typings/Types";
8+
import {
9+
focusEditor,
10+
setSelectionsWithoutFocusingEditor,
11+
} from "../util/setSelectionsAndFocusEditor";
12+
import { ensureSingleEditor } from "../util/targetUtils";
13+
import { ActionReturnValue } from "./actions.types";
14+
import { EditNew } from "./EditNew";
15+
16+
export class Paste {
17+
private editNewAction: EditNew;
18+
19+
constructor(private graph: Graph) {
20+
this.editNewAction = new EditNew(graph);
21+
}
22+
23+
async run([targets]: [Target[]]): Promise<ActionReturnValue> {
24+
const targetEditor = ensureSingleEditor(targets);
25+
const originalEditor = window.activeTextEditor;
26+
27+
const [originalCursorSelections] = await callFunctionAndUpdateSelections(
28+
this.graph.rangeUpdater,
29+
async () => {
30+
await this.editNewAction.run([targets]);
31+
},
32+
targetEditor.document,
33+
[targetEditor.selections]
34+
);
35+
36+
const originalTargetSelections = {
37+
selections: targetEditor.selections,
38+
rangeBehavior: DecorationRangeBehavior.OpenOpen,
39+
};
40+
41+
const [updatedCursorSelections, updatedTargetSelections] =
42+
await callFunctionAndUpdateSelectionsWithBehavior(
43+
this.graph.rangeUpdater,
44+
() => commands.executeCommand("editor.action.clipboardPasteAction"),
45+
targetEditor.document,
46+
[
47+
{
48+
selections: originalCursorSelections,
49+
},
50+
originalTargetSelections,
51+
]
52+
);
53+
54+
// Reset original selections
55+
// NB: We don't focus the editor here because we'll do that at the
56+
// very end.
57+
setSelectionsWithoutFocusingEditor(targetEditor, updatedCursorSelections);
58+
59+
// If necessary focus back original editor
60+
if (originalEditor != null && originalEditor !== window.activeTextEditor) {
61+
// NB: We just do one editor focus at the end, instead of using
62+
// setSelectionsAndFocusEditor because the command might operate on
63+
// multiple editors, so we just do one focus at the end.
64+
await focusEditor(originalEditor);
65+
}
66+
67+
this.graph.editStyles.displayPendingEditDecorationsForRanges(
68+
updatedTargetSelections.map((selection) => ({
69+
editor: targetEditor,
70+
range: selection,
71+
})),
72+
this.graph.editStyles.justAdded,
73+
true
74+
);
75+
76+
return {
77+
thatMark: updatedTargetSelections.map((selection) => ({
78+
editor: targetEditor,
79+
selection,
80+
})),
81+
};
82+
}
83+
}

src/core/updateSelections/updateSelections.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export async function callFunctionAndUpdateRanges(
186186
* @param rangeUpdater A RangeUpdate instance that will perform actual range updating
187187
* @param func The function to call
188188
* @param document The document containing the selections
189-
* @param selectionMatrix A matrix of selection info objects to update
189+
* @param selectionInfoMatrix A matrix of selection info objects to update
190190
* @returns The initial selections updated based upon what happened in the function
191191
*/
192192
async function callFunctionAndUpdateSelectionInfos(
@@ -207,6 +207,38 @@ async function callFunctionAndUpdateSelectionInfos(
207207
return selectionInfosToSelections(selectionInfoMatrix);
208208
}
209209

210+
/**
211+
* Performs a list of edits and returns the given selections updated based on
212+
* the applied edits
213+
* @param rangeUpdater A RangeUpdate instance that will perform actual range updating
214+
* @param func The function to call
215+
* @param document The document containing the selections
216+
* @param originalSelections The selections to update
217+
* @returns The updated selections
218+
*/
219+
export function callFunctionAndUpdateSelectionsWithBehavior(
220+
rangeUpdater: RangeUpdater,
221+
func: () => Thenable<void>,
222+
document: TextDocument,
223+
originalSelections: SelectionsWithBehavior[]
224+
) {
225+
return callFunctionAndUpdateSelectionInfos(
226+
rangeUpdater,
227+
func,
228+
document,
229+
originalSelections.map((selectionsWithBehavior) =>
230+
selectionsWithBehavior.selections.map((selection) =>
231+
getSelectionInfo(
232+
document,
233+
selection,
234+
selectionsWithBehavior.rangeBehavior ??
235+
DecorationRangeBehavior.ClosedClosed
236+
)
237+
)
238+
)
239+
);
240+
}
241+
210242
/**
211243
* Performs a list of edits and returns the given selections updated based on
212244
* the applied edits

src/processTargets/targets/BaseTarget.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,30 @@ export default abstract class BaseTarget implements Target {
147147
);
148148
}
149149

150-
isEqual(target: Target): boolean {
150+
isEqual(otherTarget: Target): boolean {
151151
return (
152-
target instanceof BaseTarget &&
153-
isEqual(this.getCloneParameters(), target.getCloneParameters())
152+
otherTarget instanceof BaseTarget &&
153+
isEqual(this.getEqualityParameters(), otherTarget.getEqualityParameters())
154154
);
155155
}
156156

157+
/**
158+
* @returns An object that can be used for determining equality between two
159+
* `BaseTarget`s
160+
*/
161+
protected getEqualityParameters() {
162+
const { thatTarget, ...otherCloneParameters } =
163+
this.getCloneParameters() as { thatTarget?: Target };
164+
if (!(thatTarget instanceof BaseTarget)) {
165+
return { thatTarget, ...otherCloneParameters };
166+
}
167+
168+
return {
169+
thatTarget: thatTarget ? thatTarget.getCloneParameters() : undefined,
170+
...otherCloneParameters,
171+
};
172+
}
173+
157174
toPositionTarget(position: Position): Target {
158175
return toPositionTarget(this, position);
159176
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: paste after argue
4+
version: 2
5+
targets:
6+
- type: primitive
7+
modifiers:
8+
- {type: position, position: after}
9+
- type: containingScope
10+
scopeType: {type: argumentOrParameter}
11+
usePrePhraseSnapshot: true
12+
action: {name: pasteFromClipboard}
13+
initialState:
14+
documentContents: foo(bar, baz, bongo)
15+
selections:
16+
- anchor: {line: 0, character: 17}
17+
active: {line: 0, character: 17}
18+
marks: {}
19+
finalState:
20+
documentContents: foo(bar, baz, bongo, baz)
21+
selections:
22+
- anchor: {line: 0, character: 17}
23+
active: {line: 0, character: 17}
24+
thatMark:
25+
- anchor: {line: 0, character: 21}
26+
active: {line: 0, character: 24}
27+
fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: position, position: after}, {type: containingScope, scopeType: {type: argumentOrParameter}}]}]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: paste after state
4+
version: 2
5+
targets:
6+
- type: primitive
7+
modifiers:
8+
- {type: position, position: after}
9+
- type: containingScope
10+
scopeType: {type: statement}
11+
usePrePhraseSnapshot: true
12+
action: {name: pasteFromClipboard}
13+
initialState:
14+
documentContents: const whatever = "hello";
15+
selections:
16+
- anchor: {line: 0, character: 9}
17+
active: {line: 0, character: 9}
18+
- anchor: {line: 0, character: 21}
19+
active: {line: 0, character: 21}
20+
marks: {}
21+
finalState:
22+
documentContents: |-
23+
const whatever = "hello";
24+
const whatever = "hello";
25+
selections:
26+
- anchor: {line: 0, character: 9}
27+
active: {line: 0, character: 9}
28+
- anchor: {line: 0, character: 21}
29+
active: {line: 0, character: 21}
30+
thatMark:
31+
- anchor: {line: 1, character: 0}
32+
active: {line: 1, character: 25}
33+
fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: position, position: after}, {type: containingScope, scopeType: {type: statement}}]}]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: paste before argue
4+
version: 2
5+
targets:
6+
- type: primitive
7+
modifiers:
8+
- {type: position, position: before}
9+
- type: containingScope
10+
scopeType: {type: argumentOrParameter}
11+
usePrePhraseSnapshot: true
12+
action: {name: pasteFromClipboard}
13+
initialState:
14+
documentContents: foo(bar, baz, bongo)
15+
selections:
16+
- anchor: {line: 0, character: 17}
17+
active: {line: 0, character: 17}
18+
marks: {}
19+
finalState:
20+
documentContents: foo(bar, baz, baz, bongo)
21+
selections:
22+
- anchor: {line: 0, character: 22}
23+
active: {line: 0, character: 22}
24+
thatMark:
25+
- anchor: {line: 0, character: 14}
26+
active: {line: 0, character: 17}
27+
fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: position, position: before}, {type: containingScope, scopeType: {type: argumentOrParameter}}]}]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: typescript
2+
command:
3+
spokenForm: paste before state
4+
version: 2
5+
targets:
6+
- type: primitive
7+
modifiers:
8+
- {type: position, position: before}
9+
- type: containingScope
10+
scopeType: {type: statement}
11+
usePrePhraseSnapshot: true
12+
action: {name: pasteFromClipboard}
13+
initialState:
14+
documentContents: const whatever = "hello";
15+
selections:
16+
- anchor: {line: 0, character: 9}
17+
active: {line: 0, character: 9}
18+
- anchor: {line: 0, character: 21}
19+
active: {line: 0, character: 21}
20+
marks: {}
21+
finalState:
22+
documentContents: |-
23+
const whatever = "hello";
24+
const whatever = "hello";
25+
selections:
26+
- anchor: {line: 1, character: 9}
27+
active: {line: 1, character: 9}
28+
- anchor: {line: 1, character: 21}
29+
active: {line: 1, character: 21}
30+
thatMark:
31+
- anchor: {line: 0, character: 0}
32+
active: {line: 0, character: 25}
33+
fullTargets: [{type: primitive, mark: {type: cursor}, modifiers: [{type: position, position: before}, {type: containingScope, scopeType: {type: statement}}]}]

0 commit comments

Comments
 (0)