Skip to content

Commit 492e56f

Browse files
AndrewDantpre-commit-ci[bot]AndreasArvidssonpokey
authored
implement 'short paint' scope type (#737)
* implement 'small paint' scope type * additional test with white space on each side * updates to get small paint working besides removal range * stage refactors recommended during pair programming * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * revert some refactors after merging * Updates small paint to stop on any surrounding pairs' delimiter * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * some additional small paint tests * rename small paint stage * remove unnecessary changed * update default phrase from small paint to short paint * update short paint every behavior * update short paint to call process surrounding pair directly * fix short paint stage capitalization * fix short paint modifier capitalization * update small paint every tests and scope type in tests * refactor short paint * Switch to strong containment implementation * fix bug with paint selection type * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix a reference to short paint stage name * remove outdated tests * boundedNonWhitespaceStage -> BoundedNonWhitespaceStage * Cleanup * paint and short paint tests * better change every paint pair tests Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Andreas Arvidsson <[email protected]> Co-authored-by: Pokey Rule <[email protected]>
1 parent 603dbd0 commit 492e56f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1104
-33
lines changed

cursorless-talon/src/modifiers/containing_scope.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"file": "document",
5454
"line": "line",
5555
"paint": "nonWhitespaceSequence",
56+
"short paint": "boundedNonWhitespaceSequence",
5657
"link": "url",
5758
"token": "token",
5859
}

src/processTargets/getModifierStage.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ import {
3131
UrlStage,
3232
} from "./modifiers/scopeTypeStages/RegexStage";
3333
import TokenStage from "./modifiers/scopeTypeStages/TokenStage";
34+
import BoundedNonWhitespaceSequenceStage, {
35+
BoundedNonWhitespaceSequenceModifier,
36+
} from "./modifiers/BoundedNonWhitespaceStage";
3437
import SurroundingPairStage from "./modifiers/SurroundingPairStage";
3538
import { ModifierStage } from "./PipelineStages.types";
3639

@@ -89,6 +92,10 @@ const getContainingScopeStage = (
8992
return new NonWhitespaceSequenceStage(
9093
modifier as NonWhitespaceSequenceModifier
9194
);
95+
case "boundedNonWhitespaceSequence":
96+
return new BoundedNonWhitespaceSequenceStage(
97+
modifier as BoundedNonWhitespaceSequenceModifier
98+
);
9299
case "url":
93100
return new UrlStage(modifier as UrlModifier);
94101
case "surroundingPair":
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Target } from "../../typings/target.types";
2+
import {
3+
ContainingScopeModifier,
4+
EveryScopeModifier,
5+
} from "../../typings/targetDescriptor.types";
6+
import { ProcessedTargetsContext } from "../../typings/Types";
7+
import { ModifierStage } from "../PipelineStages.types";
8+
import { TokenTarget } from "../targets";
9+
import getModifierStage from "../getModifierStage";
10+
import { processSurroundingPair } from "./surroundingPair";
11+
import { NoContainingScopeError } from "../../errors";
12+
13+
export type BoundedNonWhitespaceSequenceModifier = (
14+
| ContainingScopeModifier
15+
| EveryScopeModifier
16+
) & {
17+
scopeType: { type: "boundedNonWhitespaceSequence" };
18+
};
19+
20+
/**
21+
* Intersection of NonWhitespaceSequenceStage and a surrounding pair
22+
* Expand the target until reaching a white space or surrounding pair.
23+
* If there is no surrounding pair defaults to the non white space sequence
24+
*/
25+
export default class BoundedNonWhitespaceSequenceStage
26+
implements ModifierStage
27+
{
28+
constructor(private modifier: BoundedNonWhitespaceSequenceModifier) {}
29+
30+
run(context: ProcessedTargetsContext, target: Target): Target[] {
31+
const paintStage = getModifierStage({
32+
type: this.modifier.type,
33+
scopeType: { type: "nonWhitespaceSequence" },
34+
});
35+
36+
const paintTargets = paintStage.run(context, target);
37+
38+
const pairInfo = processSurroundingPair(
39+
context,
40+
target.editor,
41+
target.contentRange,
42+
{
43+
type: "surroundingPair",
44+
delimiter: "any",
45+
requireStrongContainment: true,
46+
}
47+
);
48+
49+
if (pairInfo == null) {
50+
return paintTargets;
51+
}
52+
53+
const targets = paintTargets.flatMap((paintTarget) => {
54+
const contentRange = paintTarget.contentRange.intersection(
55+
pairInfo.interiorRange
56+
);
57+
58+
if (contentRange == null || contentRange.isEmpty) {
59+
return [];
60+
}
61+
62+
return [
63+
new TokenTarget({
64+
editor: target.editor,
65+
isReversed: target.isReversed,
66+
contentRange,
67+
}),
68+
];
69+
});
70+
71+
if (targets.length === 0) {
72+
throw new NoContainingScopeError(this.modifier.scopeType.type);
73+
}
74+
75+
return targets;
76+
}
77+
}

src/processTargets/modifiers/scopeTypeStages/RegexStage.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ class RegexStage implements ModifierStage {
3838
for (let i = start.line; i <= end.line; ++i) {
3939
this.getMatchesForLine(editor, i).forEach((range) => {
4040
// Regex match and selection intersects
41-
if (range.end.isAfterOrEqual(start) && range.end.isBeforeOrEqual(end)) {
41+
if (
42+
range.end.isAfterOrEqual(start) &&
43+
range.start.isBeforeOrEqual(end)
44+
) {
4245
targets.push(this.getTargetFromRange(target, range));
4346
}
4447
});

src/processTargets/modifiers/surroundingPair/findDelimiterPairAdjacentToSelection.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import { SurroundingPairScopeType } from "../../../typings/targetDescriptor.types";
2+
import { findOppositeDelimiter } from "./findOppositeDelimiter";
13
import { getSurroundingPairOffsets } from "./getSurroundingPairOffsets";
24
import {
3-
SurroundingPairOffsets,
5+
DelimiterOccurrence,
46
Offsets,
57
PossibleDelimiterOccurrence,
6-
DelimiterOccurrence,
8+
SurroundingPairOffsets,
79
} from "./types";
8-
import { findOppositeDelimiter } from "./findOppositeDelimiter";
10+
import { weaklyContains } from "./weaklyContains";
911

1012
/**
1113
* Looks for a surrounding pair where one of its delimiters contains the entire selection.
@@ -28,7 +30,7 @@ export function findDelimiterPairAdjacentToSelection(
2830
initialIndex: number,
2931
delimiterOccurrences: PossibleDelimiterOccurrence[],
3032
selectionOffsets: Offsets,
31-
forceDirection: "left" | "right" | undefined,
33+
scopeType: SurroundingPairScopeType,
3234
bailOnUnmatchedAdjacent: boolean = false
3335
): SurroundingPairOffsets | null {
3436
const indicesToTry = [initialIndex + 1, initialIndex];
@@ -38,8 +40,7 @@ export function findDelimiterPairAdjacentToSelection(
3840

3941
if (
4042
delimiterOccurrence != null &&
41-
delimiterOccurrence.offsets.start <= selectionOffsets.start &&
42-
delimiterOccurrence.offsets.end >= selectionOffsets.end
43+
weaklyContains(delimiterOccurrence.offsets, selectionOffsets)
4344
) {
4445
const { delimiterInfo } = delimiterOccurrence;
4546

@@ -48,14 +49,23 @@ export function findDelimiterPairAdjacentToSelection(
4849
delimiterOccurrences,
4950
index,
5051
delimiterInfo,
51-
forceDirection
52+
scopeType.forceDirection
5253
);
5354

5455
if (possibleMatch != null) {
55-
return getSurroundingPairOffsets(
56+
const surroundingPairOffsets = getSurroundingPairOffsets(
5657
delimiterOccurrence as DelimiterOccurrence,
5758
possibleMatch
5859
);
60+
61+
if (
62+
!scopeType.requireStrongContainment ||
63+
(surroundingPairOffsets.leftDelimiter.start <
64+
selectionOffsets.start &&
65+
surroundingPairOffsets.rightDelimiter.end > selectionOffsets.end)
66+
) {
67+
return surroundingPairOffsets;
68+
}
5969
} else if (bailOnUnmatchedAdjacent) {
6070
return null;
6171
}

src/processTargets/modifiers/surroundingPair/findSurroundingPairCore.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { sortedIndexBy } from "lodash";
2-
import { SimpleSurroundingPairName } from "../../../typings/targetDescriptor.types";
2+
import {
3+
SimpleSurroundingPairName,
4+
SurroundingPairScopeType,
5+
} from "../../../typings/targetDescriptor.types";
36
import { findDelimiterPairAdjacentToSelection } from "./findDelimiterPairAdjacentToSelection";
47
import { findDelimiterPairContainingSelection } from "./findDelimiterPairContainingSelection";
58
import {
@@ -30,7 +33,7 @@ import {
3033
* @returns
3134
*/
3235
export function findSurroundingPairCore(
33-
forceDirection: "left" | "right" | undefined,
36+
scopeType: SurroundingPairScopeType,
3437
delimiterOccurrences: PossibleDelimiterOccurrence[],
3538
acceptableDelimiters: SimpleSurroundingPairName[],
3639
selectionOffsets: Offsets,
@@ -57,7 +60,7 @@ export function findSurroundingPairCore(
5760
initialIndex,
5861
delimiterOccurrences,
5962
selectionOffsets,
60-
forceDirection,
63+
scopeType,
6164
bailOnUnmatchedAdjacent
6265
);
6366

src/processTargets/modifiers/surroundingPair/findSurroundingPairParseTreeBased.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Range, TextDocument, TextEditor } from "vscode";
22
import { SyntaxNode } from "web-tree-sitter";
33
import {
44
SimpleSurroundingPairName,
5-
SurroundingPairDirection,
5+
SurroundingPairScopeType,
66
} from "../../../typings/targetDescriptor.types";
77
import { getNodeRange } from "../../../util/nodeSelectors";
88
import { isContainedInErrorNode } from "../../../util/treeSitterUtils";
@@ -62,7 +62,7 @@ export function findSurroundingPairParseTreeBased(
6262
selection: Range,
6363
node: SyntaxNode,
6464
delimiters: SimpleSurroundingPairName[],
65-
forceDirection: "left" | "right" | undefined
65+
scopeType: SurroundingPairScopeType
6666
) {
6767
const document: TextDocument = editor.document;
6868

@@ -88,7 +88,7 @@ export function findSurroundingPairParseTreeBased(
8888
individualDelimiters,
8989
delimiters,
9090
selectionOffsets,
91-
forceDirection,
91+
scopeType,
9292
};
9393

9494
// Walk up the parse tree from parent to parent until we find a node whose
@@ -149,7 +149,7 @@ interface Context {
149149
*/
150150
selectionOffsets: Offsets;
151151

152-
forceDirection: SurroundingPairDirection | undefined;
152+
scopeType: SurroundingPairScopeType;
153153
}
154154

155155
/**
@@ -170,7 +170,7 @@ function findSurroundingPairContainedInNode(
170170
individualDelimiters,
171171
delimiters,
172172
selectionOffsets,
173-
forceDirection,
173+
scopeType,
174174
} = context;
175175

176176
/**
@@ -213,7 +213,7 @@ function findSurroundingPairContainedInNode(
213213
// approach might not always work, but seems to work in the
214214
// languages we've tried.
215215
let side =
216-
delimiterInfo.side === "unknown" && forceDirection == null
216+
delimiterInfo.side === "unknown" && scopeType.forceDirection == null
217217
? inferDelimiterSide(delimiterNode)
218218
: delimiterInfo.side;
219219

@@ -227,7 +227,7 @@ function findSurroundingPairContainedInNode(
227227

228228
// Just run core algorithm once we have our list of delimiters.
229229
return findSurroundingPairCore(
230-
forceDirection,
230+
scopeType,
231231
delimiterOccurrences,
232232
delimiters,
233233
selectionOffsets,

src/processTargets/modifiers/surroundingPair/findSurroundingPairTextBased.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { escapeRegExp, findLast, uniq } from "lodash";
22
import { Range, TextDocument, TextEditor } from "vscode";
33
import {
44
SimpleSurroundingPairName,
5-
SurroundingPairDirection,
65
SurroundingPairName,
6+
SurroundingPairScopeType,
77
} from "../../../typings/targetDescriptor.types";
88
import { getDocumentRange } from "../../../util/range";
99
import { matchAll } from "../../../util/regex";
@@ -70,7 +70,7 @@ export function findSurroundingPairTextBased(
7070
range: Range,
7171
allowableRange: Range | null,
7272
delimiters: SimpleSurroundingPairName[],
73-
forceDirection: "left" | "right" | undefined
73+
scopeType: SurroundingPairScopeType
7474
) {
7575
const document: TextDocument = editor.document;
7676
const fullRange = allowableRange ?? getDocumentRange(document);
@@ -106,7 +106,7 @@ export function findSurroundingPairTextBased(
106106
* Context to pass to nested call
107107
*/
108108
const context: Context = {
109-
forceDirection,
109+
scopeType,
110110
delimiterRegex,
111111
delimiters,
112112
delimiterTextToDelimiterInfoMap,
@@ -196,7 +196,7 @@ function getDelimiterRegex(individualDelimiters: IndividualDelimiter[]) {
196196
* Context to pass to nested call
197197
*/
198198
interface Context {
199-
forceDirection: SurroundingPairDirection | undefined;
199+
scopeType: SurroundingPairScopeType;
200200
delimiterTextToDelimiterInfoMap: {
201201
[k: string]: IndividualDelimiter;
202202
};
@@ -229,11 +229,12 @@ function getDelimiterPairOffsets(
229229
isAtEndOfFullRange: boolean
230230
): SurroundingPairOffsets | null {
231231
const {
232-
forceDirection,
232+
scopeType,
233233
delimiterTextToDelimiterInfoMap,
234234
delimiterRegex,
235235
delimiters,
236236
} = context;
237+
const { forceDirection } = scopeType;
237238

238239
// XXX: The below is a bit wasteful when there are multiple targets, because
239240
// this whole function gets run once per target, so we're re-running this
@@ -290,7 +291,7 @@ function getDelimiterPairOffsets(
290291

291292
// Then just run core algorithm
292293
const surroundingPair = findSurroundingPairCore(
293-
forceDirection,
294+
scopeType,
294295
delimiterOccurrences,
295296
delimiters,
296297
selectionOffsets,

src/processTargets/modifiers/surroundingPair/index.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,22 @@ import { findSurroundingPairTextBased } from "./findSurroundingPairTextBased";
2121
* smallest pair of delimiters which contains the selection.
2222
*
2323
* @param context Context to be leveraged by modifier
24-
* @param selection The selection to process
25-
* @param modifier The surrounding pair modifier information
24+
* @param editor The editor containing the range
25+
* @param range The range to process
26+
* @param scopeType The surrounding pair modifier information
2627
* @returns The new selection expanded to the containing surrounding pair or
2728
* `null` if none was found
2829
*/
2930
export function processSurroundingPair(
3031
context: ProcessedTargetsContext,
3132
editor: TextEditor,
3233
range: Range,
33-
modifier: SurroundingPairScopeType
34+
scopeType: SurroundingPairScopeType
3435
): SurroundingPairInfo | null {
3536
const document = editor.document;
3637
const delimiters = complexDelimiterMap[
37-
modifier.delimiter as ComplexSurroundingPairName
38-
] ?? [modifier.delimiter];
38+
scopeType.delimiter as ComplexSurroundingPairName
39+
] ?? [scopeType.delimiter];
3940

4041
let node: SyntaxNode | null;
4142
let textFragmentExtractor: TextFragmentExtractor;
@@ -53,7 +54,7 @@ export function processSurroundingPair(
5354
range,
5455
null,
5556
delimiters,
56-
modifier.forceDirection
57+
scopeType
5758
);
5859
} else {
5960
throw err;
@@ -73,7 +74,7 @@ export function processSurroundingPair(
7374
range,
7475
textFragmentRange,
7576
delimiters,
76-
modifier.forceDirection
77+
scopeType
7778
);
7879

7980
if (surroundingRange != null) {
@@ -89,6 +90,6 @@ export function processSurroundingPair(
8990
range,
9091
node,
9192
delimiters,
92-
modifier.forceDirection
93+
scopeType
9394
);
9495
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Offsets } from "./types";
2+
3+
/**
4+
* Determines whether {@link offsets1} weakly contains {@link offsets2}, which
5+
* defined as the boundaries of {@link offsets1} being inside or equal to the
6+
* boundaries of {@link offsets2}.
7+
* @param offsets1 The first set of offsets
8+
* @param offsets2 The second set of offsets
9+
* @returns `true` if {@link offsets1} weakly contains {@link offsets2}
10+
*/
11+
export function weaklyContains(offsets1: Offsets, offsets2: Offsets) {
12+
return offsets1.start <= offsets2.start && offsets1.end >= offsets2.end;
13+
}

0 commit comments

Comments
 (0)