Skip to content

Commit 1bcaf1d

Browse files
committed
Cleanup; more tests
1 parent e8bc2bc commit 1bcaf1d

File tree

2 files changed

+116
-78
lines changed

2 files changed

+116
-78
lines changed

packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/generateDecorationsForCharacterRange/handleMultipleLines.test.ts

Lines changed: 66 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
import assert = require("assert");
2-
import { BorderStyle, DecorationStyle } from "../decorationStyle.types";
2+
import { BorderStyle } from "../decorationStyle.types";
33
import { handleMultipleLines } from "./handleMultipleLines";
44
import { Range } from "@cursorless/common";
5+
import { map } from "itertools";
56

6-
type CharacterOffsets = [number, number];
7+
const solid = BorderStyle.solid;
8+
const porous = BorderStyle.porous;
9+
const none = BorderStyle.none;
710

8-
interface StyledOffsets {
9-
style: DecorationStyle;
10-
offsets: CharacterOffsets;
11-
}
11+
type CharacterOffsets = [number, number];
1212

1313
/**
14-
* Compact representation of an input to `handleMultipleLines`. The first
15-
* element is the character offsets of the first line, and the rest are the
16-
* character offsets of the end of the remaining lines. We use a single number
17-
* for lines after the first because they always start at character 0.
14+
* Compact representation of an input to `handleMultipleLines`:
15+
*
16+
* [firstLine, ...rest]
17+
*
18+
* The first element is the character offsets of the first line, and the rest
19+
* are the character offsets of the end of the remaining lines. We use a single
20+
* number for lines after the first because they always start at character 0.
1821
*/
1922
type Input = [CharacterOffsets, ...number[]];
2023

2124
/**
22-
* Compact representation of the expected highlights for a single line. The
23-
* first element is the line number, the second is the character offsets, and
24-
* the third is the border styles for the top, bottom, left, and right borders
25-
* respectively.
25+
* Compact representation of the expected highlights for a single line:
26+
*
27+
* [lineNumber, [start, end], [top, right, bottom, left]
28+
*
29+
* The first element is the line number, the second is the character offsets of
30+
* the highlight, and the third is the border style for each side of the
31+
* highlight.
2632
*/
2733
type LineDecorations = [
2834
number,
@@ -35,10 +41,6 @@ interface TestCase {
3541
expected: LineDecorations[];
3642
}
3743

38-
const solid = BorderStyle.solid;
39-
const porous = BorderStyle.porous;
40-
const none = BorderStyle.none;
41-
4244
const testCases: TestCase[] = [
4345
{
4446
input: [[0, 1], 1],
@@ -71,28 +73,63 @@ const testCases: TestCase[] = [
7173
[2, [0, 0], [porous, solid, solid, porous]],
7274
],
7375
},
76+
{
77+
input: [[2, 3], 1],
78+
expected: [
79+
[0, [2, 3], [solid, porous, solid, solid]],
80+
[1, [0, 1], [solid, solid, solid, porous]],
81+
],
82+
},
83+
{
84+
input: [[1, 3], 4, 2],
85+
expected: [
86+
[0, [1, 3], [solid, porous, none, solid]],
87+
88+
[1, [0, 1], [solid, none, none, porous]],
89+
[1, [1, 2], [none, none, none, none]],
90+
[1, [2, 3], [none, none, solid, none]],
91+
[1, [3, 4], [porous, porous, solid, none]],
92+
93+
[2, [0, 2], [none, solid, solid, porous]],
94+
],
95+
},
96+
{
97+
input: [[0, 2], 1],
98+
expected: [
99+
[0, [0, 1], [solid, none, none, solid]],
100+
[0, [1, 2], [solid, porous, solid, none]],
101+
[1, [0, 1], [none, solid, solid, porous]],
102+
],
103+
},
104+
{
105+
input: [[0, 2], 1, 0],
106+
expected: [
107+
[0, [0, 1], [solid, none, none, solid]],
108+
[0, [1, 2], [solid, porous, porous, none]],
109+
[1, [0, 1], [none, porous, solid, porous]],
110+
[2, [0, 0], [none, solid, solid, porous]],
111+
],
112+
},
74113
];
75114

76115
suite("handleMultipleLines", () => {
77116
for (const testCase of testCases) {
78117
test(JSON.stringify(testCase.input), () => {
79118
const [firstLine, ...rest] = testCase.input;
80119

81-
const actual = [
82-
...handleMultipleLines([
120+
const actual: LineDecorations[] = map(
121+
handleMultipleLines([
83122
new Range(0, firstLine[0], 0, firstLine[1]),
84123
...rest.map((end, index) => new Range(index + 1, 0, index + 1, end)),
85124
]),
86-
];
87-
assert.deepStrictEqual(
88-
actual,
89-
testCase.expected.map(
90-
([lineNumber, [start, end], [top, right, bottom, left]]) => ({
91-
range: new Range(lineNumber, start, lineNumber, end),
92-
style: { top, right, bottom, left },
93-
}),
94-
),
125+
({ range, style }) => [
126+
range.start.line,
127+
[range.start.character, range.end.character],
128+
[style.top, style.right, style.bottom, style.left],
129+
],
95130
);
131+
132+
assert.deepStrictEqual(actual, testCase.expected);
96133
});
97134
}
98135
});

packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/generateDecorationsForCharacterRange/handleMultipleLines.ts

Lines changed: 50 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function* handleMultipleLines(
3737
* previous and next lines.
3838
*/
3939
function* handleLine(lineInfo: LineInfo): Iterable<StyledRange> {
40-
const { lineNumber, previousLine, currentLine, nextLine } = lineInfo;
40+
const { lineNumber, currentLine, nextLine } = lineInfo;
4141

4242
/** A list of "events", corresponding to the start or end of a line */
4343
const events: Event[] = getEvents(lineInfo);
@@ -47,39 +47,36 @@ function* handleLine(lineInfo: LineInfo): Iterable<StyledRange> {
4747
* the fly.
4848
*/
4949
const currentDecoration: Omit<DecorationStyle, "right"> = {
50-
// Draw a solid top line if whatever is above us isn't part of our range.
51-
// Otherwise draw no line so it merges in with the line above.
52-
top:
53-
previousLine == null || previousLine.isFirst
54-
? BorderStyle.solid
55-
: BorderStyle.none,
56-
// Analogous to above, but only care if we're last; doesn't matter if next
57-
// line is last because it is guaranteed to start at char 0
50+
// Start with a solid top border. We'll switch to no border when previous
51+
// line begins. Don't need to worry about porous because only the first
52+
// line can start after char 0.
53+
top: BorderStyle.solid,
54+
55+
// Start with a solid bottom border if we're the last line, otherwise no
56+
// border because we'll blend with the next line.
5857
bottom: currentLine.isLast ? BorderStyle.solid : BorderStyle.none,
58+
5959
// Start with a porous border if we're continuing from previous line
6060
left: currentLine.isFirst ? BorderStyle.solid : BorderStyle.porous,
6161
};
6262

6363
let currentOffset = currentLine.start;
6464
let yieldedAnything = false;
65-
let isDone = false;
66-
67-
for (const { offset, lineType, isStart } of events) {
68-
if (isDone) {
69-
break;
70-
}
7165

72-
if (offset > currentOffset) {
66+
// NB: The `loop` label here allows us to break out of the loop from inside
67+
// the switch statement.
68+
loop: for (const event of events) {
69+
if (event.offset > currentOffset) {
7370
// If we've moved forward at all since the last event, yield a decoration
7471
// for the range between the last event and this one.
7572
yield {
76-
range: new Range(lineNumber, currentOffset, lineNumber, offset),
73+
range: new Range(lineNumber, currentOffset, lineNumber, event.offset),
7774
style: {
7875
...currentDecoration,
7976
// If we're done with this line, draw a border, otherwise don't, so that
8077
// it merges in with the next decoration for this line.
8178
right:
82-
offset === currentLine.end
79+
event.offset === currentLine.end
8380
? currentLine.isLast
8481
? BorderStyle.solid
8582
: BorderStyle.porous
@@ -88,40 +85,30 @@ function* handleLine(lineInfo: LineInfo): Iterable<StyledRange> {
8885
};
8986
yieldedAnything = true;
9087
currentDecoration.left = BorderStyle.none;
88+
currentOffset = event.offset;
9189
}
9290

93-
switch (lineType) {
91+
switch (event.lineType) {
9492
case LineType.previous:
9593
// Use no top border when overlapping with previous line so it visually
9694
// merges; otherwise use porous border to show nice cutoff effect.
97-
currentDecoration.top = isStart ? BorderStyle.none : BorderStyle.porous;
95+
currentDecoration.top = event.isLineStart
96+
? BorderStyle.none
97+
: BorderStyle.porous;
9898
break;
99-
case LineType.current:
100-
if (!isStart) {
101-
isDone = true;
102-
}
99+
case LineType.current: // event.isLineStart === false
100+
break loop;
101+
case LineType.next: // event.isLineStart === false
102+
currentDecoration.bottom = nextLine!.isLast
103+
? BorderStyle.solid
104+
: BorderStyle.porous;
103105
break;
104-
case LineType.next:
105-
// Blend with next line while it is overlapping with us; then switch
106-
// to solid or porous, depending if it is the last line.
107-
if (isStart) {
108-
currentDecoration.bottom = BorderStyle.none;
109-
} else {
110-
currentDecoration.bottom = nextLine!.isLast
111-
? BorderStyle.solid
112-
: BorderStyle.porous;
113-
}
114-
break;
115-
}
116-
117-
if (currentOffset < offset) {
118-
// This guard is necessary so we don't accidentally jump backward if an
119-
// adjacent line starts before we do.
120-
currentOffset = offset;
121106
}
122107
}
123108

124109
if (!yieldedAnything) {
110+
// If current line is empty, then we didn't yield anything in the loop above,
111+
// so yield a decoration for the whole line.
125112
yield {
126113
range: new Range(
127114
lineNumber,
@@ -137,12 +124,26 @@ function* handleLine(lineInfo: LineInfo): Iterable<StyledRange> {
137124
}
138125
}
139126

140-
interface Event {
127+
interface PreviousLineEvent {
141128
offset: number;
142-
lineType: LineType;
143-
isStart: boolean;
129+
lineType: LineType.previous;
130+
isLineStart: boolean;
144131
}
145132

133+
interface CurrentLineEvent {
134+
offset: number;
135+
lineType: LineType.current;
136+
isLineStart: false;
137+
}
138+
139+
interface NextLineEvent {
140+
offset: number;
141+
lineType: LineType.next;
142+
isLineStart: false;
143+
}
144+
145+
type Event = PreviousLineEvent | CurrentLineEvent | NextLineEvent;
146+
146147
enum LineType {
147148
previous = -1,
148149
current = 0,
@@ -162,12 +163,12 @@ function getEvents({ previousLine, currentLine, nextLine }: LineInfo) {
162163
{
163164
offset: previousLine.start,
164165
lineType: LineType.previous,
165-
isStart: true,
166+
isLineStart: true,
166167
},
167168
{
168169
offset: previousLine.end,
169170
lineType: LineType.previous,
170-
isStart: false,
171+
isLineStart: false,
171172
},
172173
);
173174
}
@@ -177,14 +178,14 @@ function getEvents({ previousLine, currentLine, nextLine }: LineInfo) {
177178
events.push({
178179
offset: currentLine.end,
179180
lineType: LineType.current,
180-
isStart: false,
181+
isLineStart: false,
181182
});
182183

183184
if (nextLine != null) {
184185
events.push({
185186
offset: nextLine.end,
186187
lineType: LineType.next,
187-
isStart: false,
188+
isLineStart: false,
188189
});
189190
}
190191

@@ -198,7 +199,7 @@ function getEvents({ previousLine, currentLine, nextLine }: LineInfo) {
198199
if (a.lineType === LineType.current) {
199200
return 1;
200201
}
201-
return a.isStart ? -1 : 1;
202+
return a.isLineStart ? -1 : 1;
202203
}
203204

204205
return a.offset - b.offset;

0 commit comments

Comments
 (0)