Skip to content

Commit 25015d8

Browse files
committed
feat(playground): search with grit in the playground
1 parent 42afc79 commit 25015d8

File tree

12 files changed

+404
-17
lines changed

12 files changed

+404
-17
lines changed

src/playground/CodeMirror.tsx

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { Diagnostic as BiomeDiagnostic } from "@biomejs/wasm-web";
22
import type { Diagnostic as CodeMirrorDiagnostic } from "@codemirror/lint";
33
import { lintGutter, setDiagnostics } from "@codemirror/lint";
44
import type { Extension } from "@codemirror/state";
5-
import { EditorView } from "@codemirror/view";
5+
import { Annotation, StateField } from "@codemirror/state";
6+
import { Decoration, type DecorationSet, EditorView } from "@codemirror/view";
67
import type {
78
ReactCodeMirrorProps,
89
ReactCodeMirrorRef,
@@ -13,8 +14,46 @@ import { spanInBytesToSpanInCodeUnits, useTheme } from "@/playground/utils";
1314

1415
export type BiomeExtension = Extension;
1516

17+
const yellowHighlight = Decoration.mark({ class: "gritql-match" });
18+
19+
const gritQueryMatchesAnnotation = Annotation.define<[number, number][]>();
20+
21+
function buildDecorations(
22+
matches: [number, number][],
23+
doc: string,
24+
): DecorationSet {
25+
const decorations = [];
26+
27+
for (const [from, to] of matches) {
28+
const [codeUnitFrom, codeUnitTo] = spanInBytesToSpanInCodeUnits(
29+
[from, to],
30+
doc,
31+
);
32+
decorations.push(yellowHighlight.range(codeUnitFrom, codeUnitTo));
33+
}
34+
35+
return Decoration.set(decorations);
36+
}
37+
38+
const gritQueryMatchesField = StateField.define<DecorationSet>({
39+
create() {
40+
return Decoration.none;
41+
},
42+
update(matches, tr) {
43+
const newMatches = tr.annotation(gritQueryMatchesAnnotation);
44+
if (newMatches !== undefined) {
45+
return buildDecorations(newMatches, tr.state.doc.toString());
46+
}
47+
return matches.map(tr.changes);
48+
},
49+
provide: (f) => {
50+
return EditorView.decorations.from(f);
51+
},
52+
});
53+
1654
interface Props extends ReactCodeMirrorProps {
1755
diagnostics?: BiomeDiagnostic[];
56+
gritQueryMatches?: [number, number][];
1857
}
1958

2059
function getDiagnosticMessage(diagnostic: BiomeDiagnostic): string {
@@ -73,12 +112,8 @@ function biomeDiagnosticsToCodeMirror(
73112
return codeMirror;
74113
}
75114

76-
function getDefaultExtensions(extensions: Extension[] = []) {
77-
return [EditorView.lineWrapping, ...extensions];
78-
}
79-
80115
export default forwardRef<ReactCodeMirrorRef, Props>(function CodeMirror(
81-
{ diagnostics, ...props },
116+
{ diagnostics, gritQueryMatches, ...props },
82117
ref,
83118
) {
84119
const theme = useTheme();
@@ -90,12 +125,21 @@ export default forwardRef<ReactCodeMirrorRef, Props>(function CodeMirror(
90125
}
91126

92127
const extensions = useMemo(() => {
128+
const baseExtensions = [
129+
EditorView.lineWrapping,
130+
...(props.extensions ?? []),
131+
];
132+
133+
if (gritQueryMatches && gritQueryMatches.length > 0) {
134+
return [gritQueryMatchesField, ...baseExtensions];
135+
}
136+
93137
if (diagnostics === undefined || diagnostics.length === 0) {
94-
return getDefaultExtensions(props.extensions);
138+
return baseExtensions;
95139
}
96140

97-
return [lintGutter(), ...getDefaultExtensions(props.extensions)];
98-
}, [diagnostics, props.extensions]);
141+
return [lintGutter(), ...baseExtensions];
142+
}, [diagnostics, props.extensions, gritQueryMatches]);
99143

100144
useEffect(() => {
101145
if (editor !== undefined && diagnostics !== undefined) {
@@ -111,6 +155,14 @@ export default forwardRef<ReactCodeMirrorRef, Props>(function CodeMirror(
111155
}
112156
}, [editor, diagnostics]);
113157

158+
useEffect(() => {
159+
if (editor !== undefined && gritQueryMatches !== undefined) {
160+
editor.dispatch({
161+
annotations: [gritQueryMatchesAnnotation.of(gritQueryMatches)],
162+
});
163+
}
164+
}, [editor, gritQueryMatches]);
165+
114166
return (
115167
<RealCodeMirror
116168
{...props}

src/playground/Playground.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export default function Playground({
6363
const file = getFileState(playgroundState, playgroundState.currentFile);
6464
const biomeOutput = file.biome;
6565
const prettierOutput = file.prettier;
66+
const gritQuery = file.gritQuery ?? "";
67+
const gritQueryResults = biomeOutput.gritQuery ?? { matches: [] };
68+
const gritTargetLanguage = playgroundState.settings.gritTargetLanguage;
6669

6770
const codeMirrorExtensions = useMemo(() => {
6871
if (isJsonFilename(playgroundState.currentFile)) {
@@ -193,6 +196,7 @@ export default function Playground({
193196
onChange={onChange}
194197
autoFocus={true}
195198
data-testid="editor"
199+
gritQueryMatches={gritQueryResults.matches}
196200
/>
197201
);
198202

@@ -353,6 +357,30 @@ export default function Playground({
353357
console={biomeOutput.diagnostics.console}
354358
diagnostics={biomeOutput.diagnostics.list}
355359
code={code}
360+
gritQuery={gritQuery}
361+
gritQueryResults={gritQueryResults}
362+
gritTargetLanguage={gritTargetLanguage}
363+
onGritQueryChange={(query) => {
364+
setPlaygroundState((state) => ({
365+
...state,
366+
files: {
367+
...state.files,
368+
[state.currentFile]: {
369+
...getFileState(state, state.currentFile),
370+
gritQuery: query,
371+
},
372+
},
373+
}));
374+
}}
375+
onLanguageChange={(language) => {
376+
setPlaygroundState((state) => ({
377+
...state,
378+
settings: {
379+
...state.settings,
380+
gritTargetLanguage: language,
381+
},
382+
}));
383+
}}
356384
/>
357385
</Resizable>
358386
</div>

src/playground/PlaygroundLoader.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import {
4444
normalizeFilename,
4545
} from "@/playground/utils";
4646

47-
function throttle(callback: () => void): () => void {
48-
const timeout = setTimeout(callback, 100);
47+
function throttle(callback: () => void, delay = 100): () => void {
48+
const timeout = setTimeout(callback, delay);
4949

5050
return () => {
5151
clearTimeout(timeout);
@@ -243,18 +243,24 @@ function PlaygroundLoader() {
243243
code: getCurrentCode(state),
244244
});
245245

246+
const file = getFileState(state, state.currentFile);
247+
246248
workerRef.current?.postMessage({
247249
type: "update",
248250
cursorPosition: state.cursorPosition,
249251
filename: state.currentFile,
250252
code: getCurrentCode(state),
253+
gritQuery: file?.gritQuery,
254+
defaultLanguage: state.settings.gritTargetLanguage,
251255
});
252256
});
253257
}, [
254258
loadingState,
255259
state.currentFile,
256260
state.cursorPosition,
261+
state.settings.gritTargetLanguage,
257262
getCurrentCode(state),
263+
getFileState(state, state.currentFile)?.gritQuery,
258264
]);
259265

260266
switch (loadingState) {
@@ -305,6 +311,11 @@ function buildLocation(state: PlaygroundState): string {
305311
if (language !== LANGUAGE.TSX) {
306312
queryStringObj.language = language;
307313
}
314+
315+
const gritQuery = getFileState(state, state.currentFile)?.gritQuery;
316+
if (gritQuery) {
317+
queryStringObj.gritQuery = gritQuery;
318+
}
308319
} else {
309320
// Populate files
310321
for (const filename in state.files) {
@@ -361,11 +372,16 @@ function initState(
361372
language: (searchParams.get("language") as Language) ?? LANGUAGE.TSX,
362373
script: searchParams.get("script") === "true",
363374
});
364-
files[`main.${ext}`] = {
375+
const gritQuery = searchParams.get("gritQuery");
376+
const baseFile = {
365377
content: decodeCode(searchParams.get("code") ?? ""),
366378
biome: emptyBiomeOutput,
367379
prettier: emptyPrettierOutput,
368380
};
381+
files[`main.${ext}`] =
382+
gritQuery !== null && gritQuery !== ""
383+
? { ...baseFile, gritQuery }
384+
: baseFile;
369385
hasFiles = true;
370386
}
371387
}
@@ -399,6 +415,7 @@ function initState(
399415
lineWidth: Number.parseInt(
400416
searchParams.get("lineWidth") ??
401417
String(defaultPlaygroundState.settings.lineWidth),
418+
10,
402419
),
403420
indentStyle:
404421
(searchParams.get("indentStyle") as IndentStyle) ??
@@ -420,6 +437,7 @@ function initState(
420437
indentWidth: Number.parseInt(
421438
searchParams.get("indentWidth") ??
422439
String(defaultPlaygroundState.settings.indentWidth),
440+
10,
423441
),
424442
semicolons:
425443
(searchParams.get("semicolons") as Semicolons) ??
@@ -476,6 +494,11 @@ function initState(
476494
tailwindDirectives:
477495
searchParams.get("tailwindDirectives") === "true" ||
478496
defaultPlaygroundState.settings.tailwindDirectives,
497+
gritTargetLanguage:
498+
(searchParams.get("gritTargetLanguage") as
499+
| "JavaScript"
500+
| "CSS"
501+
| undefined) ?? defaultPlaygroundState.settings.gritTargetLanguage,
479502
},
480503
};
481504
}

src/playground/components/DiagnosticsPane.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,34 @@ import { type RefObject, useState } from "react";
44
import Tabs from "@/playground/components/Tabs";
55
import DiagnosticsConsoleTab from "@/playground/tabs/DiagnosticsConsoleTab";
66
import DiagnosticsListTab from "@/playground/tabs/DiagnosticsListTab";
7+
import GritQLSearchTab from "@/playground/tabs/GritQLSearchTab";
78

89
interface Props {
910
editorRef: RefObject<ReactCodeMirrorRef | null>;
1011
console: string;
1112
diagnostics: Diagnostic[];
1213
code: string;
14+
gritQuery: string;
15+
gritQueryResults: { matches: [number, number][]; error?: string };
16+
gritTargetLanguage: "JavaScript" | "CSS";
17+
onGritQueryChange: (query: string) => void;
18+
onLanguageChange: (language: "JavaScript" | "CSS") => void;
1319
}
1420

1521
export default function DiagnosticsPane({
1622
editorRef,
1723
diagnostics,
1824
console,
1925
code,
26+
gritQuery,
27+
gritQueryResults,
28+
gritTargetLanguage,
29+
onGritQueryChange,
30+
onLanguageChange,
2031
}: Props) {
21-
const [tab, setTab] = useState<"diagnostics" | "console">("diagnostics");
32+
const [tab, setTab] = useState<"diagnostics" | "console" | "gritql">(
33+
"diagnostics",
34+
);
2235

2336
return (
2437
<Tabs
@@ -42,6 +55,21 @@ export default function DiagnosticsPane({
4255
title: "Console",
4356
children: <DiagnosticsConsoleTab console={console} />,
4457
},
58+
{
59+
key: "gritql",
60+
title: "GritQL",
61+
children: (
62+
<GritQLSearchTab
63+
editorRef={editorRef}
64+
code={code}
65+
gritQuery={gritQuery}
66+
gritQueryResults={gritQueryResults}
67+
gritTargetLanguage={gritTargetLanguage}
68+
onGritQueryChange={onGritQueryChange}
69+
onLanguageChange={onLanguageChange}
70+
/>
71+
),
72+
},
4573
]}
4674
/>
4775
);

0 commit comments

Comments
 (0)