Skip to content

Commit b6b22f8

Browse files
Add rewrap action (#365)
* Initial attempt * Initial working version * Add tests * Add another test Co-authored-by: Andreas Arvidsson <andreas.arvidsson87@gmail.com>
1 parent d855232 commit b6b22f8

File tree

13 files changed

+322
-39
lines changed

13 files changed

+322
-39
lines changed

images/squareRepackHarp.gif

16.5 KB
Loading

src/actions/Rewrap.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { TextEditor } from "vscode";
2+
import {
3+
Action,
4+
ActionPreferences,
5+
ActionReturnValue,
6+
Graph,
7+
SelectionWithContext,
8+
TypedSelection,
9+
} from "../typings/Types";
10+
import { repeat } from "../util/array";
11+
12+
export default class Rewrap implements Action {
13+
getTargetPreferences: () => ActionPreferences[] = () => [
14+
{
15+
insideOutsideType: "inside",
16+
modifier: {
17+
type: "surroundingPair",
18+
delimiter: "any",
19+
delimiterInclusion: undefined,
20+
},
21+
},
22+
];
23+
24+
constructor(private graph: Graph) {
25+
this.run = this.run.bind(this);
26+
}
27+
28+
run(
29+
[targets]: [TypedSelection[]],
30+
left: string,
31+
right: string
32+
): Promise<ActionReturnValue> {
33+
const boundaries: TypedSelection[] = targets.flatMap((target) => {
34+
const boundary = target.selectionContext.boundary;
35+
36+
if (boundary == null || boundary.length !== 2) {
37+
throw Error("Target must have an opening and closing delimiter");
38+
}
39+
40+
return boundary.map((edge) =>
41+
constructSimpleTypedSelection(target.selection.editor, edge)
42+
);
43+
});
44+
45+
const replacementTexts = repeat([left, right], targets.length);
46+
47+
return this.graph.actions.replace.run([boundaries], replacementTexts);
48+
}
49+
}
50+
51+
function constructSimpleTypedSelection(
52+
editor: TextEditor,
53+
selection: SelectionWithContext
54+
): TypedSelection {
55+
return {
56+
selection: {
57+
selection: selection.selection,
58+
editor,
59+
},
60+
selectionType: "token",
61+
selectionContext: selection.context,
62+
insideOutsideType: null,
63+
position: "contents",
64+
};
65+
}

src/actions/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Sort, Reverse } from "./Sort";
2929
import Call from "./Call";
3030
import WrapWithSnippet from "./WrapWithSnippet";
3131
import Deselect from "./Deselect";
32+
import Rewrap from "./Rewrap";
3233

3334
class Actions implements ActionRecord {
3435
constructor(private graph: Graph) {}
@@ -57,6 +58,7 @@ class Actions implements ActionRecord {
5758
replace = new Replace(this.graph);
5859
replaceWithTarget = new Bring(this.graph);
5960
reverseTargets = new Reverse(this.graph);
61+
rewrapWithPairedDelimiter = new Rewrap(this.graph);
6062
scrollToBottom = new ScrollToBottom(this.graph);
6163
scrollToCenter = new ScrollToCenter(this.graph);
6264
scrollToTop = new ScrollToTop(this.graph);

src/processTargets/modifiers/surroundingPair/extractSelectionFromSurroundingPairOffsets.ts

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,45 @@ export function extractSelectionFromSurroundingPairOffsets(
2020
surroundingPairOffsets: SurroundingPairOffsets,
2121
delimiterInclusion: DelimiterInclusion
2222
): SelectionWithContext[] {
23+
const interior = [
24+
{
25+
selection: new Selection(
26+
document.positionAt(
27+
baseOffset + surroundingPairOffsets.leftDelimiter.end
28+
),
29+
document.positionAt(
30+
baseOffset + surroundingPairOffsets.rightDelimiter.start
31+
)
32+
),
33+
context: {},
34+
},
35+
];
36+
37+
const boundary = [
38+
{
39+
selection: new Selection(
40+
document.positionAt(
41+
baseOffset + surroundingPairOffsets.leftDelimiter.start
42+
),
43+
document.positionAt(
44+
baseOffset + surroundingPairOffsets.leftDelimiter.end
45+
)
46+
),
47+
context: {},
48+
},
49+
{
50+
selection: new Selection(
51+
document.positionAt(
52+
baseOffset + surroundingPairOffsets.rightDelimiter.start
53+
),
54+
document.positionAt(
55+
baseOffset + surroundingPairOffsets.rightDelimiter.end
56+
)
57+
),
58+
context: {},
59+
},
60+
];
61+
2362
// If delimiter inclusion is null, do default behavior and include the
2463
// delimiters
2564
if (delimiterInclusion == null) {
@@ -33,50 +72,18 @@ export function extractSelectionFromSurroundingPairOffsets(
3372
baseOffset + surroundingPairOffsets.rightDelimiter.end
3473
)
3574
),
36-
context: {},
75+
context: {
76+
boundary,
77+
interior,
78+
},
3779
},
3880
];
3981
}
4082

4183
switch (delimiterInclusion) {
4284
case "interiorOnly":
43-
return [
44-
{
45-
selection: new Selection(
46-
document.positionAt(
47-
baseOffset + surroundingPairOffsets.leftDelimiter.end
48-
),
49-
document.positionAt(
50-
baseOffset + surroundingPairOffsets.rightDelimiter.start
51-
)
52-
),
53-
context: {},
54-
},
55-
];
85+
return interior;
5686
case "excludeInterior":
57-
return [
58-
{
59-
selection: new Selection(
60-
document.positionAt(
61-
baseOffset + surroundingPairOffsets.leftDelimiter.start
62-
),
63-
document.positionAt(
64-
baseOffset + surroundingPairOffsets.leftDelimiter.end
65-
)
66-
),
67-
context: {},
68-
},
69-
{
70-
selection: new Selection(
71-
document.positionAt(
72-
baseOffset + surroundingPairOffsets.rightDelimiter.start
73-
),
74-
document.positionAt(
75-
baseOffset + surroundingPairOffsets.rightDelimiter.end
76-
)
77-
),
78-
context: {},
79-
},
80-
];
87+
return boundary;
8188
}
8289
}

src/processTargets/processSelectionType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ function getTokenSelectionContext(
226226
}
227227

228228
return {
229+
...selectionContext,
229230
isInDelimitedList,
230231
containingListDelimiter: " ",
231232
leadingDelimiterRange: isInDelimitedList ? leadingDelimiterRange : null,
232233
trailingDelimiterRange: isInDelimitedList ? trailingDelimiterRange : null,
233-
outerSelection: selectionContext.outerSelection,
234234
};
235235
}
236236

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
languageId: plaintext
2+
command:
3+
version: 1
4+
spokenForm: curly repack round
5+
action: rewrapWithPairedDelimiter
6+
targets:
7+
- type: primitive
8+
modifier: {type: surroundingPair, delimiter: parentheses}
9+
extraArgs: ['{', '}']
10+
initialState:
11+
documentContents: |-
12+
([hello])
13+
(there)
14+
selections:
15+
- anchor: {line: 0, character: 5}
16+
active: {line: 0, character: 5}
17+
- anchor: {line: 1, character: 5}
18+
active: {line: 1, character: 5}
19+
marks: {}
20+
finalState:
21+
documentContents: |-
22+
{[hello]}
23+
{there}
24+
selections:
25+
- anchor: {line: 0, character: 5}
26+
active: {line: 0, character: 5}
27+
- anchor: {line: 1, character: 5}
28+
active: {line: 1, character: 5}
29+
thatMark:
30+
- anchor: {line: 0, character: 0}
31+
active: {line: 0, character: 1}
32+
- anchor: {line: 0, character: 8}
33+
active: {line: 0, character: 9}
34+
- anchor: {line: 1, character: 0}
35+
active: {line: 1, character: 1}
36+
- anchor: {line: 1, character: 6}
37+
active: {line: 1, character: 7}
38+
fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: parentheses}}]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
languageId: plaintext
2+
command:
3+
version: 1
4+
spokenForm: square repack harp
5+
action: rewrapWithPairedDelimiter
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: h}
9+
extraArgs: ['[', ']']
10+
initialState:
11+
documentContents: |
12+
(hello)
13+
selections:
14+
- anchor: {line: 1, character: 0}
15+
active: {line: 1, character: 0}
16+
marks:
17+
default.h:
18+
start: {line: 0, character: 1}
19+
end: {line: 0, character: 6}
20+
finalState:
21+
documentContents: |
22+
[hello]
23+
selections:
24+
- anchor: {line: 1, character: 0}
25+
active: {line: 1, character: 0}
26+
thatMark:
27+
- anchor: {line: 0, character: 0}
28+
active: {line: 0, character: 1}
29+
- anchor: {line: 0, character: 6}
30+
active: {line: 0, character: 7}
31+
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: h}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
languageId: plaintext
2+
command:
3+
version: 1
4+
spokenForm: square repack leper
5+
action: rewrapWithPairedDelimiter
6+
targets:
7+
- type: primitive
8+
mark: {type: decoratedSymbol, symbolColor: default, character: (}
9+
extraArgs: ['[', ']']
10+
initialState:
11+
documentContents: |
12+
(hello)
13+
selections:
14+
- anchor: {line: 1, character: 0}
15+
active: {line: 1, character: 0}
16+
marks:
17+
default.(:
18+
start: {line: 0, character: 0}
19+
end: {line: 0, character: 1}
20+
finalState:
21+
documentContents: |
22+
[hello]
23+
selections:
24+
- anchor: {line: 1, character: 0}
25+
active: {line: 1, character: 0}
26+
thatMark:
27+
- anchor: {line: 0, character: 0}
28+
active: {line: 0, character: 1}
29+
- anchor: {line: 0, character: 6}
30+
active: {line: 0, character: 7}
31+
fullTargets: [{type: primitive, mark: {type: decoratedSymbol, symbolColor: default, character: (}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
languageId: plaintext
2+
command:
3+
version: 1
4+
spokenForm: square repack pair
5+
action: rewrapWithPairedDelimiter
6+
targets:
7+
- type: primitive
8+
modifier: {type: surroundingPair, delimiter: any}
9+
extraArgs: ['[', ']']
10+
initialState:
11+
documentContents: |
12+
(hello)
13+
selections:
14+
- anchor: {line: 0, character: 2}
15+
active: {line: 0, character: 2}
16+
marks: {}
17+
finalState:
18+
documentContents: |
19+
[hello]
20+
selections:
21+
- anchor: {line: 0, character: 2}
22+
active: {line: 0, character: 2}
23+
thatMark:
24+
- anchor: {line: 0, character: 0}
25+
active: {line: 0, character: 1}
26+
- anchor: {line: 0, character: 6}
27+
active: {line: 0, character: 7}
28+
fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
languageId: plaintext
2+
command:
3+
version: 1
4+
spokenForm: square repack this
5+
action: rewrapWithPairedDelimiter
6+
targets:
7+
- type: primitive
8+
mark: {type: cursor}
9+
extraArgs: ['[', ']']
10+
initialState:
11+
documentContents: (hello)
12+
selections:
13+
- anchor: {line: 0, character: 4}
14+
active: {line: 0, character: 4}
15+
marks: {}
16+
finalState:
17+
documentContents: "[hello]"
18+
selections:
19+
- anchor: {line: 0, character: 4}
20+
active: {line: 0, character: 4}
21+
thatMark:
22+
- anchor: {line: 0, character: 0}
23+
active: {line: 0, character: 1}
24+
- anchor: {line: 0, character: 6}
25+
active: {line: 0, character: 7}
26+
fullTargets: [{type: primitive, mark: {type: cursor}, selectionType: token, position: contents, insideOutsideType: inside, modifier: {type: surroundingPair, delimiter: any}}]

0 commit comments

Comments
 (0)