|
1 | | -import { Range } from "@cursorless/common"; |
| 1 | +import { |
| 2 | + Range, |
| 3 | + type ScopeSupportFacet, |
| 4 | + type ScopeSupportFacetInfo, |
| 5 | + type TextualScopeSupportFacet, |
| 6 | +} from "@cursorless/common"; |
2 | 7 | import React, { useState } from "react"; |
3 | 8 | import scopeTestsJson from "../../../../../static/scopeTests.json"; |
4 | 9 | import { Code, type Highlight } from "./Code"; |
5 | 10 | import type { Fixture, ScopeTestsJson } from "./types"; |
| 11 | +import { |
| 12 | + getFacetInfo, |
| 13 | + prettifyFacet, |
| 14 | + prettifyScopeType, |
| 15 | + serializeScopeType, |
| 16 | +} from "./util"; |
| 17 | + |
| 18 | +const scopeTests = scopeTestsJson as ScopeTestsJson; |
6 | 19 |
|
7 | 20 | type RangeType = "content" | "removal"; |
8 | 21 |
|
9 | | -const scopeTests = scopeTestsJson as ScopeTestsJson; |
| 22 | +interface Scope { |
| 23 | + scope: string; |
| 24 | + facets: Facet[]; |
| 25 | +} |
| 26 | + |
| 27 | +interface Facet { |
| 28 | + facet: ScopeSupportFacet | TextualScopeSupportFacet; |
| 29 | + name: string; |
| 30 | + info: ScopeSupportFacetInfo; |
| 31 | + fixtures: Fixture[]; |
| 32 | +} |
10 | 33 |
|
11 | 34 | interface Props { |
12 | 35 | languageId: string; |
13 | 36 | } |
14 | 37 |
|
15 | 38 | export function ScopeVisualizer({ languageId }: Props) { |
16 | | - const [fixtures] = useState(getFixtures(languageId)); |
| 39 | + const [scopes] = useState(getScopeFixtures(languageId)); |
17 | 40 | const [rangeType, setRangeType] = useState<RangeType>("content"); |
18 | 41 | const [renderWhitespace, setRenderWhitespace] = useState(false); |
19 | 42 |
|
| 43 | + const renderOptions = () => { |
| 44 | + return ( |
| 45 | + <div className="mb-4"> |
| 46 | + <select |
| 47 | + value={rangeType} |
| 48 | + onChange={(e) => setRangeType(e.target.value as RangeType)} |
| 49 | + > |
| 50 | + <option value="content">Content</option> |
| 51 | + <option value="removal">Removal</option> |
| 52 | + </select> |
| 53 | + |
| 54 | + <label className="ml-1"> |
| 55 | + <input |
| 56 | + type="checkbox" |
| 57 | + checked={renderWhitespace} |
| 58 | + onChange={(e) => setRenderWhitespace(e.target.checked)} |
| 59 | + /> |
| 60 | + Render whitespace |
| 61 | + </label> |
| 62 | + </div> |
| 63 | + ); |
| 64 | + }; |
| 65 | + |
| 66 | + return ( |
| 67 | + <> |
| 68 | + {renderOptions()} |
| 69 | + |
| 70 | + {scopes.map((scope) => |
| 71 | + renderScope(languageId, rangeType, renderWhitespace, scope), |
| 72 | + )} |
| 73 | + </> |
| 74 | + ); |
| 75 | +} |
| 76 | + |
| 77 | +function renderScope( |
| 78 | + languageId: string, |
| 79 | + rangeType: RangeType, |
| 80 | + renderWhitespace: boolean, |
| 81 | + scope: Scope, |
| 82 | +) { |
20 | 83 | return ( |
21 | | - <div> |
22 | | - <select |
23 | | - value={rangeType} |
24 | | - onChange={(e) => setRangeType(e.target.value as RangeType)} |
25 | | - > |
26 | | - <option value="content">Content</option> |
27 | | - <option value="removal">Removal</option> |
28 | | - </select> |
29 | | - |
30 | | - <label> |
31 | | - <input |
32 | | - type="checkbox" |
33 | | - checked={renderWhitespace} |
34 | | - onChange={(e) => setRenderWhitespace(e.target.checked)} |
35 | | - /> |
36 | | - Render whitespace |
37 | | - </label> |
38 | | - |
39 | | - {fixtures.map((f) => |
40 | | - renderFixture(languageId, rangeType, renderWhitespace, f), |
| 84 | + <div key={scope.scope}> |
| 85 | + <h3>[{scope.scope}]</h3> |
| 86 | + {scope.facets.map((f) => |
| 87 | + renderFacet(languageId, rangeType, renderWhitespace, f), |
41 | 88 | )} |
42 | 89 | </div> |
43 | 90 | ); |
44 | 91 | } |
45 | 92 |
|
46 | | -function renderFixture( |
| 93 | +function renderFacet( |
47 | 94 | languageId: string, |
48 | 95 | rangeType: RangeType, |
49 | 96 | renderWhitespace: boolean, |
50 | | - fixture: Fixture, |
| 97 | + facet: Facet, |
51 | 98 | ) { |
52 | | - const highlights = getHighlights(fixture, rangeType); |
53 | 99 | return ( |
54 | | - <div key={fixture.name}> |
55 | | - {fixture.facet} |
56 | | - <Code |
57 | | - languageId={languageId} |
58 | | - renderWhitespace={renderWhitespace} |
59 | | - highlights={highlights} |
60 | | - > |
61 | | - {fixture.code} |
62 | | - </Code> |
| 100 | + <div key={facet.facet}> |
| 101 | + <span className="facet-name" title={facet.facet}> |
| 102 | + {facet.name} |
| 103 | + </span> |
| 104 | + <p>{facet.info.description}</p> |
| 105 | + {facet.fixtures.map((fixture) => ( |
| 106 | + <Code |
| 107 | + key={fixture.name} |
| 108 | + languageId={languageId} |
| 109 | + renderWhitespace={renderWhitespace} |
| 110 | + highlights={getHighlights(fixture, rangeType)} |
| 111 | + > |
| 112 | + {fixture.code} |
| 113 | + </Code> |
| 114 | + ))} |
63 | 115 | </div> |
64 | 116 | ); |
65 | 117 | } |
@@ -138,9 +190,49 @@ function getOverlap(a: Range, b: Range): Range | null { |
138 | 190 | : null; |
139 | 191 | } |
140 | 192 |
|
141 | | -function getFixtures(languageId: string): Fixture[] { |
| 193 | +function getScopeFixtures(languageId: string): Scope[] { |
142 | 194 | const languageIds = new Set<string>( |
143 | 195 | scopeTests.imports[languageId] ?? [languageId], |
144 | 196 | ); |
145 | | - return scopeTests.fixtures.filter((f) => languageIds.has(f.languageId)); |
| 197 | + const fixtures = scopeTests.fixtures.filter((f) => |
| 198 | + languageIds.has(f.languageId), |
| 199 | + ); |
| 200 | + const map: Record<string, Record<string, Facet>> = {}; |
| 201 | + |
| 202 | + for (const fixture of fixtures) { |
| 203 | + const info = getFacetInfo(fixture.languageId, fixture.facet); |
| 204 | + let scope = serializeScopeType(info.scopeType); |
| 205 | + |
| 206 | + if (scope.startsWith("private.")) { |
| 207 | + continue; |
| 208 | + } |
| 209 | + |
| 210 | + scope = prettifyScopeType(scope); |
| 211 | + |
| 212 | + const facet = prettifyFacet(fixture.facet, true); |
| 213 | + if (map[scope] == null) { |
| 214 | + map[scope] = {}; |
| 215 | + } |
| 216 | + if (map[scope][fixture.facet] == null) { |
| 217 | + map[scope][fixture.facet] = { |
| 218 | + facet: fixture.facet, |
| 219 | + name: facet, |
| 220 | + info, |
| 221 | + fixtures: [], |
| 222 | + }; |
| 223 | + } |
| 224 | + map[scope][fixture.facet].fixtures.push(fixture); |
| 225 | + } |
| 226 | + |
| 227 | + return Object.keys(map) |
| 228 | + .sort() |
| 229 | + .map((scope): Scope => { |
| 230 | + const data = map[scope]; |
| 231 | + const facets = Object.keys(data) |
| 232 | + .sort() |
| 233 | + .map((facet): Facet => { |
| 234 | + return data[facet]; |
| 235 | + }); |
| 236 | + return { scope, facets }; |
| 237 | + }); |
146 | 238 | } |
0 commit comments