Skip to content

Commit f1e7b4c

Browse files
AndreasArvidssonpokeypre-commit-ci[bot]
authored
One of compound scope type (#1069)
* Draft off one of compound scope type * Tweaks * More tweaks * Tweak implementation * Fix a couple bugs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: Pokey Rule <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 78fa2b3 commit f1e7b4c

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { maxBy, minBy } from "lodash";
2+
import { Position, Range, TextEditor } from "vscode";
3+
import { getScopeHandler } from ".";
4+
import {
5+
Direction,
6+
OneOfScopeType,
7+
} from "../../../typings/targetDescriptor.types";
8+
import { OutOfRangeError } from "../targetSequenceUtils";
9+
import type { TargetScope } from "./scope.types";
10+
import { ScopeHandler } from "./scopeHandler.types";
11+
12+
export default class OneOfScopeHandler implements ScopeHandler {
13+
private scopeHandlers: ScopeHandler[] = this.scopeType.scopeTypes.map(
14+
(scopeType) => {
15+
const handler = getScopeHandler(scopeType, this.languageId);
16+
if (handler == null) {
17+
throw new Error(`No available scope handler for '${scopeType.type}'`);
18+
}
19+
return handler;
20+
},
21+
);
22+
23+
public iterationScopeType: OneOfScopeType = {
24+
type: "oneOf",
25+
scopeTypes: this.scopeHandlers.map(
26+
({ iterationScopeType }) => iterationScopeType,
27+
),
28+
};
29+
30+
constructor(
31+
public readonly scopeType: OneOfScopeType,
32+
private languageId: string,
33+
) {}
34+
35+
getScopesTouchingPosition(
36+
editor: TextEditor,
37+
position: Position,
38+
ancestorIndex?: number,
39+
): TargetScope[] {
40+
if (ancestorIndex !== 0) {
41+
// FIXME: We could support this one, but it will be a bit of work.
42+
throw new Error("`grand` not yet supported for compound scopes.");
43+
}
44+
45+
return keepOnlyBottomLevelScopes(
46+
this.scopeHandlers.flatMap((scopeHandler) =>
47+
scopeHandler.getScopesTouchingPosition(editor, position, ancestorIndex),
48+
),
49+
);
50+
}
51+
52+
/**
53+
* We proceed as follows:
54+
*
55+
* 1. Get all scopes returned by
56+
* {@link ScopeHandler.getScopesOverlappingRange} from each of
57+
* {@link scopeHandlers}.
58+
* 2. If any of these scopes has a {@link TargetScope.domain|domain} that
59+
* terminates within {@link range}, return all such maximal scopes.
60+
* 3. Otherwise, return a list containing just the minimal scope containing
61+
* {@link range}.
62+
*/
63+
getScopesOverlappingRange(editor: TextEditor, range: Range): TargetScope[] {
64+
const candidateScopes = this.scopeHandlers.flatMap((scopeHandler) =>
65+
scopeHandler.getScopesOverlappingRange(editor, range),
66+
);
67+
68+
const scopesTerminatingInRange = candidateScopes.filter(
69+
({ domain }) => !domain.contains(range),
70+
);
71+
72+
return scopesTerminatingInRange.length > 0
73+
? keepOnlyTopLevelScopes(scopesTerminatingInRange)
74+
: keepOnlyBottomLevelScopes(candidateScopes);
75+
}
76+
77+
getScopeRelativeToPosition(
78+
editor: TextEditor,
79+
position: Position,
80+
offset: number,
81+
direction: Direction,
82+
): TargetScope {
83+
let currentPosition = position;
84+
let currentScope: TargetScope;
85+
86+
if (this.scopeHandlers.length === 0) {
87+
throw new OutOfRangeError();
88+
}
89+
90+
for (let i = 0; i < offset; i++) {
91+
const candidateScopes = this.scopeHandlers.map((scopeHandler) =>
92+
scopeHandler.getScopeRelativeToPosition(
93+
editor,
94+
currentPosition,
95+
1,
96+
direction,
97+
),
98+
);
99+
100+
currentScope =
101+
direction === "forward"
102+
? minBy(candidateScopes, ({ domain: start }) => start)!
103+
: maxBy(candidateScopes, ({ domain: end }) => end)!;
104+
105+
currentPosition =
106+
direction === "forward"
107+
? currentScope.domain.end
108+
: currentScope.domain.start;
109+
}
110+
111+
return currentScope!;
112+
}
113+
}
114+
115+
function keepOnlyTopLevelScopes(candidateScopes: TargetScope[]): TargetScope[] {
116+
return candidateScopes.filter(
117+
({ domain }) =>
118+
!candidateScopes.some(({ domain: otherDomain }) =>
119+
otherDomain.contains(domain),
120+
),
121+
);
122+
}
123+
124+
function keepOnlyBottomLevelScopes(
125+
candidateScopes: TargetScope[],
126+
): TargetScope[] {
127+
return candidateScopes.filter(
128+
({ domain }) =>
129+
!candidateScopes.some(({ domain: otherDomain }) =>
130+
domain.contains(otherDomain),
131+
),
132+
);
133+
}

src/processTargets/modifiers/scopeHandlers/getScopeHandler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
LineScopeHandler,
66
TokenScopeHandler,
77
WordScopeHandler,
8+
OneOfScopeHandler,
89
} from ".";
910
import type { ScopeType } from "../../../typings/targetDescriptor.types";
1011
import type { ScopeHandler } from "./scopeHandler.types";
@@ -43,6 +44,8 @@ export default function getScopeHandler(
4344
return new LineScopeHandler(scopeType, languageId);
4445
case "document":
4546
return new DocumentScopeHandler(scopeType, languageId);
47+
case "oneOf":
48+
return new OneOfScopeHandler(scopeType, languageId);
4649
default:
4750
return undefined;
4851
}

src/processTargets/modifiers/scopeHandlers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ export * from "./TokenScopeHandler";
1212
export { default as TokenScopeHandler } from "./TokenScopeHandler";
1313
export * from "./DocumentScopeHandler";
1414
export { default as DocumentScopeHandler } from "./DocumentScopeHandler";
15+
export * from "./OneOfScopeHandler";
16+
export { default as OneOfScopeHandler } from "./OneOfScopeHandler";
1517
export * from "./getScopeHandler";
1618
export { default as getScopeHandler } from "./getScopeHandler";

src/typings/targetDescriptor.types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,16 @@ export interface SurroundingPairScopeType {
156156
requireStrongContainment?: boolean;
157157
}
158158

159+
export interface OneOfScopeType {
160+
type: "oneOf";
161+
scopeTypes: ScopeType[];
162+
}
163+
159164
export type ScopeType =
160165
| SimpleScopeType
161166
| SurroundingPairScopeType
162-
| CustomRegexScopeType;
167+
| CustomRegexScopeType
168+
| OneOfScopeType;
163169

164170
export interface ContainingSurroundingPairModifier
165171
extends ContainingScopeModifier {

0 commit comments

Comments
 (0)