Skip to content

Commit 87ebee8

Browse files
maciejklimekpokey
andauthored
Add surrounding pair modifier (#3) (#168)
* Add surrounding pair modifier (#3) * delimeter => delimiter * Clean up * Tweak * More tweak Co-authored-by: Pokey Rule <[email protected]>
1 parent 7ded70a commit 87ebee8

40 files changed

+1425
-34
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ dist
33
node_modules
44
.vscode-test/
55
*.vsix
6+
package-lock.json

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,4 @@
221221
"immutability-helper": "^3.1.1",
222222
"lodash": "^4.17.21"
223223
}
224-
}
224+
}

src/Types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export type Delimiter =
6868
| "parentheses"
6969
| "singleQuotes"
7070
| "doubleQuotes";
71+
7172
export type ScopeType =
7273
| "argumentOrParameter"
7374
| "arrowFunction"
@@ -97,7 +98,8 @@ export type PieceType = "word" | "character";
9798

9899
export interface SurroundingPairModifier {
99100
type: "surroundingPair";
100-
delimiter: Delimiter;
101+
delimiter: Delimiter | null;
102+
delimitersOnly: boolean;
101103
}
102104
export interface ContainingScopeModifier {
103105
type: "containingScope";

src/languages/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1+
import { SyntaxNode } from "web-tree-sitter";
2+
import { notSupported } from "../nodeMatchers";
3+
import { selectionWithEditorFromRange } from "../selectionUtils";
14
import {
25
NodeMatcher,
36
NodeMatcherValue,
47
ScopeType,
58
SelectionWithEditor,
69
} from "../Types";
10+
import csharp from "./csharp";
11+
import java from "./java";
712
import json from "./json";
813
import python from "./python";
914
import typescript from "./typescript";
10-
import csharp from "./csharp";
11-
import java from "./java";
12-
import { notSupported } from "../nodeMatchers";
13-
import { SyntaxNode } from "web-tree-sitter";
14-
import { selectionWithEditorFromRange } from "../selectionUtils";
1515

1616
const languageMatchers: Record<string, Record<ScopeType, NodeMatcher>> = {
1717
csharp: csharp,

src/languages/surroundingPair.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { maxBy, zip } from "lodash";
2+
import { Position, Selection } from "vscode";
3+
import { Point, SyntaxNode } from "web-tree-sitter";
4+
import {
5+
Delimiter,
6+
NodeMatcher,
7+
NodeMatcherValue,
8+
SelectionWithEditor,
9+
} from "../Types";
10+
11+
function positionFromPoint(point: Point): Position {
12+
return new Position(point.row, point.column);
13+
}
14+
15+
const delimiterToText: Record<Delimiter, string[]> = {
16+
squareBrackets: ["[", "]"],
17+
curlyBrackets: ["{", "}"],
18+
angleBrackets: ["<", ">"],
19+
parentheses: ["(", ")"],
20+
singleQuotes: ["'", "'"],
21+
doubleQuotes: ['"', '"'],
22+
};
23+
24+
const leftToRightMap: Record<string, string> = Object.fromEntries(
25+
Object.values(delimiterToText)
26+
);
27+
28+
export function createSurroundingPairMatcher(
29+
delimiter: Delimiter | null,
30+
delimitersOnly: boolean
31+
): NodeMatcher {
32+
return function nodeMatcher(
33+
selection: SelectionWithEditor,
34+
node: SyntaxNode
35+
) {
36+
const delimitersToCheck =
37+
delimiter == null ? Object.keys(delimiterToText) : [delimiter];
38+
39+
const leftDelimiterTypes = delimitersToCheck.map(
40+
(delimiter) => delimiterToText[delimiter][0]
41+
);
42+
43+
const leftDelimiterNodes = node.children.filter(
44+
(child) =>
45+
leftDelimiterTypes.includes(child.type) &&
46+
positionFromPoint(child.startPosition).isBeforeOrEqual(
47+
selection.selection.start
48+
)
49+
);
50+
51+
if (leftDelimiterNodes.length === 0) {
52+
return null;
53+
}
54+
55+
const leftDelimiterNode = leftDelimiterNodes[leftDelimiterNodes.length - 1];
56+
const rightDelimiterType = leftToRightMap[leftDelimiterNode.type];
57+
58+
const rightDelimiterNode = node.children.find(
59+
(child) =>
60+
child.type === rightDelimiterType && child !== leftDelimiterNode
61+
);
62+
63+
if (rightDelimiterNode == null) {
64+
return null;
65+
}
66+
67+
return extractSelection(
68+
leftDelimiterNode,
69+
rightDelimiterNode,
70+
delimitersOnly
71+
);
72+
};
73+
}
74+
75+
function extractSelection(
76+
leftDelimiterNode: SyntaxNode,
77+
rightDelimiterNode: SyntaxNode,
78+
delimitersOnly: boolean
79+
): NodeMatcherValue[] {
80+
if (delimitersOnly === false) {
81+
return [
82+
{
83+
node: leftDelimiterNode,
84+
selection: {
85+
selection: new Selection(
86+
positionFromPoint(leftDelimiterNode.endPosition),
87+
positionFromPoint(rightDelimiterNode.startPosition)
88+
),
89+
context: {
90+
outerSelection: new Selection(
91+
positionFromPoint(leftDelimiterNode.startPosition),
92+
positionFromPoint(rightDelimiterNode.endPosition)
93+
),
94+
},
95+
},
96+
},
97+
];
98+
} else {
99+
return [
100+
{
101+
node: leftDelimiterNode,
102+
selection: {
103+
selection: new Selection(
104+
positionFromPoint(leftDelimiterNode.startPosition),
105+
positionFromPoint(leftDelimiterNode.endPosition)
106+
),
107+
context: {},
108+
},
109+
},
110+
{
111+
node: rightDelimiterNode,
112+
selection: {
113+
selection: new Selection(
114+
positionFromPoint(rightDelimiterNode.startPosition),
115+
positionFromPoint(rightDelimiterNode.endPosition)
116+
),
117+
context: {},
118+
},
119+
},
120+
];
121+
}
122+
}

src/processTargets.ts

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
1-
import { concat, range, zip } from "lodash";
21
import update from "immutability-helper";
2+
import { concat, range, zip } from "lodash";
3+
import * as vscode from "vscode";
4+
import { Location, Position, Range, Selection, TextDocument } from "vscode";
35
import { SyntaxNode } from "web-tree-sitter";
6+
import { SUBWORD_MATCHER } from "./constants";
47
import { getNodeMatcher } from "./languages";
5-
import { Selection, Range, Position, Location, TextDocument } from "vscode";
8+
import { createSurroundingPairMatcher } from "./languages/surroundingPair";
9+
import { performInsideOutsideAdjustment } from "./performInsideOutsideAdjustment";
10+
import {
11+
selectionFromPositions,
12+
selectionWithEditorFromPositions,
13+
selectionWithEditorFromRange,
14+
} from "./selectionUtils";
615
import {
16+
LineNumberPosition,
717
Mark,
18+
Modifier,
19+
NodeMatcher,
820
PrimitiveTarget,
921
ProcessedTargetsContext,
1022
RangeTarget,
1123
SelectionContext,
1224
SelectionWithEditor,
1325
Target,
1426
TypedSelection,
15-
Modifier,
16-
LineNumberPosition,
1727
} from "./Types";
18-
import { performInsideOutsideAdjustment } from "./performInsideOutsideAdjustment";
19-
import { SUBWORD_MATCHER } from "./constants";
20-
import {
21-
selectionFromPositions,
22-
selectionWithEditorFromPositions,
23-
selectionWithEditorFromRange,
24-
} from "./selectionUtils";
2528

2629
export default function processTargets(
2730
context: ProcessedTargetsContext,
@@ -173,6 +176,7 @@ function processSinglePrimitiveTarget(
173176
transformSelection(context, target, markSelection)
174177
)
175178
);
179+
176180
const typedSelections = transformedSelections.map(
177181
({ selection, context: selectionContext }) =>
178182
createTypedSelection(context, target, selection, selectionContext)
@@ -251,6 +255,31 @@ function getSelectionsFromMark(
251255
}
252256
}
253257

258+
function findNearestContainingAncestorNode(
259+
startNode: SyntaxNode,
260+
nodeMatcher: NodeMatcher,
261+
selection: SelectionWithEditor
262+
) {
263+
let node: SyntaxNode | null = startNode;
264+
while (node != null) {
265+
const matches = nodeMatcher(selection, node);
266+
if (matches != null) {
267+
return matches
268+
.map((match) => match.selection)
269+
.map((matchedSelection) => ({
270+
selection: selectionWithEditorFromRange(
271+
selection,
272+
matchedSelection.selection
273+
),
274+
context: matchedSelection.context,
275+
}));
276+
}
277+
node = node.parent;
278+
}
279+
280+
return null;
281+
}
282+
254283
function transformSelection(
255284
context: ProcessedTargetsContext,
256285
target: PrimitiveTarget,
@@ -268,25 +297,18 @@ function transformSelection(
268297
modifier.scopeType,
269298
modifier.includeSiblings ?? false
270299
);
271-
272300
let node: SyntaxNode | null = context.getNodeAtLocation(
273301
new Location(selection.editor.document.uri, selection.selection)
274302
);
275303

276-
while (node != null) {
277-
const matches = nodeMatcher(selection, node);
278-
if (matches != null) {
279-
return matches
280-
.map((match) => match.selection)
281-
.map((matchedSelection) => ({
282-
selection: selectionWithEditorFromRange(
283-
selection,
284-
matchedSelection.selection
285-
),
286-
context: matchedSelection.context,
287-
}));
288-
}
289-
node = node.parent;
304+
let result = findNearestContainingAncestorNode(
305+
node,
306+
nodeMatcher,
307+
selection
308+
);
309+
310+
if (result != null) {
311+
return result;
290312
}
291313

292314
throw new Error(`Couldn't find containing ${modifier.scopeType}`);
@@ -392,8 +414,32 @@ function transformSelection(
392414
}
393415

394416
case "matchingPairSymbol":
395-
case "surroundingPair":
396417
throw new Error("Not implemented");
418+
419+
case "surroundingPair":
420+
{
421+
let node: SyntaxNode | null = context.getNodeAtLocation(
422+
new vscode.Location(
423+
selection.editor.document.uri,
424+
selection.selection
425+
)
426+
);
427+
428+
const nodeMatcher = createSurroundingPairMatcher(
429+
modifier.delimiter,
430+
modifier.delimitersOnly
431+
);
432+
let result = findNearestContainingAncestorNode(
433+
node,
434+
nodeMatcher,
435+
selection
436+
);
437+
if (result != null) {
438+
return result;
439+
}
440+
}
441+
442+
throw new Error(`Couldn't find containing `);
397443
}
398444
}
399445

@@ -603,6 +649,7 @@ function getTokenSelectionContext(
603649
containingListDelimiter: " ",
604650
leadingDelimiterRange: isInDelimitedList ? leadingDelimiterRange : null,
605651
trailingDelimiterRange: isInDelimitedList ? trailingDelimiterRange : null,
652+
outerSelection: selectionContext.outerSelection,
606653
};
607654
}
608655

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
spokenForm: chuck matching
2+
languageId: python
3+
command:
4+
actionName: delete
5+
partialTargets:
6+
- type: primitive
7+
modifier:
8+
{ type: surroundingPair, delimiter: null, delimitersOnly: false }
9+
extraArgs: []
10+
marks: {}
11+
initialState:
12+
documentContents: f"j{fdfhjkd}lkjlkj"
13+
selections:
14+
- anchor: { line: 0, character: 15 }
15+
active: { line: 0, character: 15 }
16+
finalState:
17+
documentContents: ""
18+
selections:
19+
- anchor: { line: 0, character: 0 }
20+
active: { line: 0, character: 0 }
21+
thatMark:
22+
- anchor: { line: 0, character: 0 }
23+
active: { line: 0, character: 0 }
24+
fullTargets:
25+
[
26+
{
27+
type: primitive,
28+
mark: { type: cursor },
29+
selectionType: token,
30+
position: contents,
31+
modifier:
32+
{ type: surroundingPair, delimiter: null, delimitersOnly: false },
33+
insideOutsideType: outside,
34+
},
35+
]

0 commit comments

Comments
 (0)