Skip to content

Commit 286260c

Browse files
Proper handling of surrounding pair delimiter prefixes
1 parent 25a838f commit 286260c

File tree

7 files changed

+79
-48
lines changed

7 files changed

+79
-48
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
languageId: python
2+
command:
3+
version: 7
4+
spokenForm: change inside
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- {type: interiorOnly}
11+
usePrePhraseSnapshot: true
12+
initialState:
13+
documentContents: r'command server'
14+
selections:
15+
- anchor: {line: 0, character: 2}
16+
active: {line: 0, character: 2}
17+
marks: {}
18+
finalState:
19+
documentContents: r''
20+
selections:
21+
- anchor: {line: 0, character: 2}
22+
active: {line: 0, character: 2}

data/fixtures/recorded/surroundingPair/parseTree/python/changePair2.yml renamed to data/fixtures/recorded/surroundingPair/parseTree/python/changePair3.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ command:
1111
scopeType: {type: surroundingPair, delimiter: any}
1212
usePrePhraseSnapshot: true
1313
initialState:
14-
documentContents: "\" r\""
14+
documentContents: r'command server'
1515
selections:
16-
- anchor: {line: 0, character: 0}
17-
active: {line: 0, character: 0}
16+
- anchor: {line: 0, character: 2}
17+
active: {line: 0, character: 2}
1818
marks: {}
1919
finalState:
2020
documentContents: ""

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/delimiterMaps.ts

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,9 @@ interface Options {
1616
isSingleLine?: boolean;
1717

1818
/**
19-
* This field can be used to force us to treat the side of the delimiter as
20-
* unknown. We usually infer this from the fact that the opening and closing
21-
* delimiters are the same, but in some cases they are different, but the side
22-
* is actually still unknown. In particular, this is the case for Python
23-
* string prefixes, where if we see the prefix it doesn't necessarily mean
24-
* that it's an opening delimiter. For example, in `" r"`, note that the `r`
25-
* is just part of the string, not a prefix of the opening delimiter.
19+
* The prefixes that can be used before the left side of the delimiter, eg "r"
2620
*/
27-
isUnknownSide?: boolean;
21+
prefixes?: string[];
2822
}
2923

3024
type DelimiterMap = Record<
@@ -54,8 +48,6 @@ const delimiterToText: DelimiterMap = Object.freeze({
5448

5549
// https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
5650
const pythonPrefixes = [
57-
// Base case without a prefix
58-
"",
5951
// string prefixes
6052
"r",
6153
"u",
@@ -102,26 +94,10 @@ const delimiterToTextOverrides: Record<string, Partial<DelimiterMap>> = {
10294
},
10395

10496
python: {
105-
singleQuotes: [
106-
pythonPrefixes.map((prefix) => `${prefix}'`),
107-
"'",
108-
{ isSingleLine: true, isUnknownSide: true },
109-
],
110-
doubleQuotes: [
111-
pythonPrefixes.map((prefix) => `${prefix}"`),
112-
'"',
113-
{ isSingleLine: true, isUnknownSide: true },
114-
],
115-
tripleSingleQuotes: [
116-
pythonPrefixes.map((prefix) => `${prefix}'''`),
117-
"'''",
118-
{ isUnknownSide: true },
119-
],
120-
tripleDoubleQuotes: [
121-
pythonPrefixes.map((prefix) => `${prefix}"""`),
122-
'"""',
123-
{ isUnknownSide: true },
124-
],
97+
singleQuotes: ["'", "'", { isSingleLine: true, prefixes: pythonPrefixes }],
98+
doubleQuotes: ['"', '"', { isSingleLine: true, prefixes: pythonPrefixes }],
99+
tripleSingleQuotes: ["'''", "'''", { prefixes: pythonPrefixes }],
100+
tripleDoubleQuotes: ['"""', '"""', { prefixes: pythonPrefixes }],
125101
},
126102

127103
ruby: {

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterOccurrences.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,8 @@ export function getDelimiterOccurrences(
2727
const textFragments =
2828
languageDefinition?.getCaptures(document, "textFragment") ?? [];
2929

30-
const delimiterTextToDelimiterInfoMap = Object.fromEntries(
31-
individualDelimiters.map((individualDelimiter) => [
32-
individualDelimiter.text,
33-
individualDelimiter,
34-
]),
35-
);
30+
const delimiterTextToDelimiterInfoMap =
31+
getDelimiterTextToDelimiterInfoMap(individualDelimiters);
3632

3733
const text = document.getText();
3834

@@ -59,3 +55,25 @@ export function getDelimiterOccurrences(
5955
};
6056
});
6157
}
58+
59+
function getDelimiterTextToDelimiterInfoMap(
60+
individualDelimiters: IndividualDelimiter[],
61+
): Record<string, IndividualDelimiter> {
62+
return Object.fromEntries(
63+
individualDelimiters.flatMap((individualDelimiter) => {
64+
const results = [[individualDelimiter.text, individualDelimiter]];
65+
if (individualDelimiter.prefixes.length > 0) {
66+
for (const prefix of individualDelimiter.prefixes) {
67+
const prefixText = prefix + individualDelimiter.text;
68+
const prefixDelimiter: IndividualDelimiter = {
69+
...individualDelimiter,
70+
text: prefixText,
71+
side: "left",
72+
};
73+
results.push([prefixText, prefixDelimiter]);
74+
}
75+
}
76+
return results;
77+
}),
78+
);
79+
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterRegex.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@ export function getDelimiterRegex(individualDelimiters: IndividualDelimiter[]) {
1212
// Create a regex which is a disjunction of all possible left / right
1313
// delimiter texts
1414
const individualDelimiterDisjunct = uniq(
15-
individualDelimiters.map(({ text }) => text),
16-
)
17-
.map(escapeRegExp)
18-
.join("|");
15+
individualDelimiters.flatMap((delimiter) => {
16+
const text = escapeRegExp(delimiter.text);
17+
const result = [text];
18+
for (const prefix of delimiter.prefixes) {
19+
// If the prefix is only alpha character, we need to make sure that there is no preceding alpha characters.
20+
if (alphaRegex.test(prefix)) {
21+
result.push(`(?<!\\w)${prefix}${text}`);
22+
} else {
23+
result.push(`${escapeRegExp(prefix)}${text}`);
24+
}
25+
}
26+
return result;
27+
}),
28+
).join("|");
1929

2030
// Then make sure that we don't allow preceding `\`
2131
return new RegExp(`(?<!\\\\)(${individualDelimiterDisjunct})`, "gu");
2232
}
33+
34+
const alphaRegex = /^\w+$/;

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getIndividualDelimiters.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import type { IndividualDelimiter } from "./types";
2121
export function getIndividualDelimiters(
2222
delimiter: SurroundingPairName,
2323
languageId: string,
24-
) {
24+
): IndividualDelimiter[] {
2525
const delimiters = complexDelimiterMap[
2626
delimiter as ComplexSurroundingPairName
2727
] ?? [delimiter];
@@ -36,7 +36,7 @@ function getSimpleIndividualDelimiters(
3636
return delimiters.flatMap((delimiterName) => {
3737
const [leftDelimiter, rightDelimiter, options] =
3838
delimiterToText[delimiterName];
39-
const { isSingleLine = false, isUnknownSide = false } = options ?? {};
39+
const { isSingleLine = false, prefixes = [] } = options ?? {};
4040

4141
// Allow for the fact that a delimiter might have multiple ways to indicate
4242
// its opening / closing
@@ -54,9 +54,6 @@ function getSimpleIndividualDelimiters(
5454
const isRight = rightDelimiters.includes(text);
5555

5656
const side = (() => {
57-
if (isUnknownSide) {
58-
return "unknown";
59-
}
6057
if (isLeft && !isRight) {
6158
return "left";
6259
}
@@ -73,6 +70,7 @@ function getSimpleIndividualDelimiters(
7370
side,
7471
delimiterName,
7572
isSingleLine,
73+
prefixes,
7674
};
7775
});
7876
});

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export interface IndividualDelimiter {
2828
*/
2929
isSingleLine: boolean;
3030

31+
/**
32+
* The prefixes that can be used before the left side of the delimiter, eg "r"
33+
*/
34+
prefixes: string[];
35+
3136
/**
3237
* The text that can be used to represent this side of the delimiter, eg "("
3338
*/

0 commit comments

Comments
 (0)