Repository navigation
Expand file tree
/
Copy pathScopeVisualizer.tsx
More file actions
134 lines (120 loc) · 3.78 KB
/
Copy pathScopeVisualizer.tsx
File metadata and controls
134 lines (120 loc) · 3.78 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { usePluginData } from "@docusaurus/useGlobalData";
import type { Dispatch, ReactNode, SetStateAction } from "react";
import { createContext, useContext, useMemo, useState } from "react";
import { generateDecorations } from "./calculateHighlights";
import { Code } from "./Code";
import type { Fixture, RangeType } from "./types";
import { getFacetInfo } from "./util";
interface ScopeVisualizerContextValue {
fixtures: ReadonlyMap<string, Fixture>;
rangeType: RangeType;
setRangeType: Dispatch<SetStateAction<RangeType>>;
renderWhitespace: boolean;
setRenderWhitespace: Dispatch<SetStateAction<boolean>>;
}
const ScopeVisualizerContext = createContext<
ScopeVisualizerContextValue | undefined
>(undefined);
export function ScopeVisualizerProvider({ children }: { children: ReactNode }) {
const scopeTests = usePluginData("scope-tests-plugin") as Fixture[];
const [rangeType, setRangeType] = useState<RangeType>("content");
const [renderWhitespace, setRenderWhitespace] = useState(true);
const fixtures = useMemo(
() => new Map(scopeTests.map((fixture) => [fixture.name, fixture])),
[scopeTests],
);
const value = useMemo(
() => ({
fixtures,
rangeType,
setRangeType,
renderWhitespace,
setRenderWhitespace,
}),
[fixtures, rangeType, renderWhitespace],
);
return (
<ScopeVisualizerContext.Provider value={value}>
{children}
</ScopeVisualizerContext.Provider>
);
}
export function ScopeVisualizerOptions() {
const { rangeType, renderWhitespace, setRangeType, setRenderWhitespace } =
useScopeVisualizer();
return (
<div className="mb-4">
<select
className="form-select form-select-sm d-inline-block w-auto"
value={rangeType}
onChange={(event) =>
setRangeType(event.currentTarget.value as RangeType)
}
>
<option value="content">Content range</option>
<option value="removal">Removal range</option>
<option value="blend">Blended ranges</option>
</select>
<label className="ms-2">
<input
type="checkbox"
className="me-1"
checked={renderWhitespace}
onChange={(event) => setRenderWhitespace(event.currentTarget.checked)}
/>
Render whitespace
</label>
</div>
);
}
interface ScopeProps {
fixtureName: string;
languageId?: string;
}
export function ScopeVisualizer({ fixtureName, languageId }: ScopeProps) {
const { fixtures, rangeType, renderWhitespace } = useScopeVisualizer();
const fixture = fixtures.get(fixtureName);
if (fixture == null) {
throw new Error(`Unknown scope fixture: ${fixtureName}`);
}
const facetInfo = getFacetInfo(fixture.languageId, fixture.facet);
return (
<Code
// oxlint-disable-next-line react_perf/jsx-no-new-object-as-prop
link={{
name: "GitHub",
url: `https://github.com/cursorless-dev/cursorless/blob/main/resources/fixtures/${fixture.name}.scope`,
}}
languageId={languageId ?? normalizeLanguageId(fixture.languageId)}
renderWhitespace={renderWhitespace}
decorations={generateDecorations(
fixture,
rangeType,
facetInfo.isIteration ?? false,
)}
>
{fixture.code}
</Code>
);
}
function useScopeVisualizer(): ScopeVisualizerContextValue {
const value = useContext(ScopeVisualizerContext);
if (value == null) {
throw new Error(
"Scope visualizer components must be used within ScopeVisualizerProvider",
);
}
return value;
}
function normalizeLanguageId(languageId: string): string {
switch (languageId) {
case "javascript.core":
return "javascript";
case "typescript.core":
return "typescript";
case "javascript.jsx":
return "javascriptreact";
default:
return languageId;
}
}