Skip to content

Commit 49969eb

Browse files
authored
fix: keep ESQuery highlights aligned while typing (#365)
1 parent 7dc48d6 commit 49969eb

2 files changed

Lines changed: 95 additions & 10 deletions

File tree

e2e-tests/code-editing-and-ast-interaction.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
*/
44
import { expect, test } from "@playwright/test";
55

6+
type HighlightSamplesState = {
7+
intervalId: number;
8+
samples: string[];
9+
};
10+
11+
declare global {
12+
interface Window {
13+
__highlightSamples?: HighlightSamplesState;
14+
}
15+
}
16+
617
/**
718
* This test verifies that:
819
* - Users can edit code in the editor
@@ -56,3 +67,63 @@ test(`should change code, then highlight code and AST nodes matching ESQuery sel
5667
.getByRole("button", { name: "Toggle Property" })
5768
.click();
5869
});
70+
71+
test(`should keep ESQuery highlights aligned while typing before a matching literal`, async ({
72+
page,
73+
}) => {
74+
await page.goto("/");
75+
76+
const codeEditor = page
77+
.getByRole("region", { name: "Code Editor Panel" })
78+
.getByRole("textbox")
79+
.nth(1);
80+
const highlight = page.locator(".bg-editorHighlightedRangeColor");
81+
82+
await codeEditor.click();
83+
await codeEditor.press("ControlOrMeta+KeyA");
84+
await codeEditor.press("Backspace");
85+
await codeEditor.pressSequentially("42;");
86+
87+
await page
88+
.getByRole("textbox", { name: "ESQuery Selector" })
89+
.fill("Literal");
90+
91+
await expect(highlight).toHaveText("42");
92+
93+
await codeEditor.click();
94+
await codeEditor.press("Home");
95+
await page.evaluate(() => {
96+
window.__highlightSamples = {
97+
intervalId: window.setInterval(() => {
98+
window.__highlightSamples?.samples.push(
99+
Array.from(
100+
document.querySelectorAll(
101+
".bg-editorHighlightedRangeColor",
102+
),
103+
)
104+
.map(node => node.textContent ?? "")
105+
.join(""),
106+
);
107+
}, 20),
108+
samples: [],
109+
};
110+
});
111+
await codeEditor.pressSequentially("+ + + +", { delay: 50 });
112+
113+
const highlightSamples = await page.evaluate(() => {
114+
const highlightSamples = window.__highlightSamples?.samples ?? [];
115+
116+
if (window.__highlightSamples) {
117+
window.clearInterval(window.__highlightSamples.intervalId);
118+
delete window.__highlightSamples;
119+
}
120+
121+
return highlightSamples;
122+
});
123+
124+
expect(highlightSamples.length).toBeGreaterThan(0);
125+
expect(
126+
highlightSamples.every(highlightText => highlightText === "42"),
127+
).toBe(true);
128+
await expect(highlight).toHaveText("42");
129+
});

src/utils/highlighted-ranges.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
import { Decoration, ViewPlugin } from "@codemirror/view";
1+
import { Decoration, ViewPlugin, type ViewUpdate } from "@codemirror/view";
22
import type { SourceRange } from "@eslint/core";
33

44
const highlightRangeDecoration = Decoration.mark({
55
class: "bg-editorHighlightedRangeColor",
66
});
77

8+
function createHighlightedRangeDecorations(ranges: SourceRange[]) {
9+
return Decoration.set(
10+
ranges.map(([rangeFrom, rangeTo]) =>
11+
highlightRangeDecoration.range(rangeFrom, rangeTo),
12+
),
13+
true,
14+
);
15+
}
16+
817
export const highlightedRangesExtension = (ranges: SourceRange[]) =>
9-
ViewPlugin.define(() => ({}), {
10-
decorations: () =>
11-
Decoration.set(
12-
ranges.map(([rangeFrom, rangeTo]) =>
13-
highlightRangeDecoration.range(rangeFrom, rangeTo),
14-
),
15-
true,
16-
),
17-
});
18+
ViewPlugin.define(
19+
() => ({
20+
decorations: createHighlightedRangeDecorations(ranges),
21+
22+
update(update: ViewUpdate) {
23+
if (update.docChanged) {
24+
this.decorations = this.decorations.map(update.changes);
25+
}
26+
},
27+
}),
28+
{
29+
decorations: value => value.decorations,
30+
},
31+
);

0 commit comments

Comments
 (0)