-
-
Notifications
You must be signed in to change notification settings - Fork 93
Expand file tree
/
Copy pathgenerateDifferentiatedRanges.ts
More file actions
111 lines (98 loc) · 3.66 KB
/
generateDifferentiatedRanges.ts
File metadata and controls
111 lines (98 loc) · 3.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { max } from "lodash-es";
import type { GeneralizedRange } from "@cursorless/lib-common";
import {
generalizedRangeContains,
generalizedRangeTouches,
} from "@cursorless/lib-common";
import type { DifferentiatedGeneralizedRange } from "./decorationStyle.types";
/**
* Given a list of generalized ranges, returns a list of differentiated ranges,
* where any ranges that are touching have different differentiation indices.
* We ensure that nested ranges have a greater differentiation index than their
* parents, so that we can then render them in order of increasing
* differentiation index to ensure that nested ranges are rendered after their
* parents, so that we don't get strange interleaving artifacts.
* @param ranges A list of generalized ranges.
* @returns An iterable of differentiated generalized ranges.
*/
export function* generateDifferentiatedRanges(
ranges: GeneralizedRange[],
): Iterable<DifferentiatedGeneralizedRange> {
ranges.sort(compareGeneralizedRangesByStart);
/** A list of ranges that may touch the current range */
let currentRanges: DifferentiatedGeneralizedRange[] = [];
for (const range of ranges) {
// Remove any ranges that have ended before the start of the current range.
currentRanges = currentRanges.filter(({ range: previousRange }) =>
generalizedRangeTouches(previousRange, range),
);
const differentiatedRange = {
range,
differentiationIndex: getDifferentiationIndex(currentRanges, range),
} as DifferentiatedGeneralizedRange;
yield differentiatedRange;
currentRanges.push(differentiatedRange);
}
}
/**
* Returns the differentiation index to use for the given range, given a list of
* ranges that touch the current range. We return a differentiation index that
* differs from any of the given ranges, and is greater than any range
* containing {@link range}.
*
* @param currentRanges A list of ranges that touch the current range
* @param range The range to get the differentiation index for
* @returns The differentiation index to use for the given range
*/
function getDifferentiationIndex(
currentRanges: DifferentiatedGeneralizedRange[],
range: GeneralizedRange,
): number {
const maxContainingDifferentiationIndex = max(
currentRanges
.filter((r) => generalizedRangeContains(r.range, range))
.map((r) => r.differentiationIndex),
);
let i =
maxContainingDifferentiationIndex == null
? 0
: maxContainingDifferentiationIndex + 1;
for (; ; i++) {
if (
!currentRanges.some(
({ differentiationIndex }) => differentiationIndex === i,
)
) {
return i;
}
}
}
/**
* Compares two generalized ranges by their start positions, with line ranges
* sorted before character ranges that start on the same line.
* @param a A generalized range
* @param b A generalized range
* @returns -1 if {@link a} should be sorted before {@link b}, 1 if {@link b}
* should be sorted before {@link a}, and 0 if they are equal.
*/
function compareGeneralizedRangesByStart(
a: GeneralizedRange,
b: GeneralizedRange,
): number {
if (a.type === "character") {
if (b.type === "character") {
// a.type === "character" && b.type === "character"
return a.start.compareTo(b.start);
}
// a.type === "character" && b.type === "line"
// Line ranges are always sorted before character ranges that start on the
// same line.
return a.start.line === b.start ? 1 : a.start.line - b.start;
}
if (b.type === "line") {
// a.type === "line" && b.type === "line"
return a.start - b.start;
}
// a.type === "line" && b.type === "character"
return a.start === b.start.line ? -1 : a.start - b.start.line;
}