Skip to content

Commit 4e35d71

Browse files
Render fixtures grouped by scope and facet
1 parent afc1a9c commit 4e35d71

File tree

5 files changed

+203
-72
lines changed

5 files changed

+203
-72
lines changed

packages/common/src/util/serializeScopeType.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

packages/cursorless-org-docs/src/docs/user/languages/components/ScopeSupportForLevel.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import {
2-
camelCaseToAllDown,
3-
capitalize,
42
groupBy,
53
scopeSupportFacetInfos,
6-
serializeScopeType,
74
type ScopeSupportFacet,
85
type ScopeSupportFacetInfo,
96
} from "@cursorless/common";
107
import React, { useState, type JSX } from "react";
8+
import { prettifyFacet, prettifyScopeType, serializeScopeType } from "./util";
119

1210
interface Props {
1311
facets: ScopeSupportFacet[];
@@ -67,7 +65,7 @@ export function ScopeSupportForLevel({
6765
return (
6866
<li key={facetInfo.facet}>
6967
<span className="facet-name" title={facetInfo.facet}>
70-
{prettifyFacet(facetInfo.facet)}
68+
{prettifyFacet(facetInfo.facet, false)}
7169
</span>
7270
: {facetInfo.description}
7371
</li>
@@ -96,20 +94,3 @@ export function ScopeSupportForLevel({
9694
interface AugmentedFacetInfo extends ScopeSupportFacetInfo {
9795
facet: ScopeSupportFacet;
9896
}
99-
100-
function prettifyScopeType(scopeType: string): string {
101-
return capitalize(camelCaseToAllDown(scopeType));
102-
}
103-
104-
function prettifyFacet(facet: ScopeSupportFacet): string {
105-
const parts = facet.split(".").map(camelCaseToAllDown);
106-
if (parts.length === 1) {
107-
return capitalize(parts[0]);
108-
}
109-
const isIteration = parts[parts.length - 1] === "iteration";
110-
if (isIteration) {
111-
parts.pop();
112-
}
113-
const name = capitalize(parts.slice(1).join(" "));
114-
return isIteration ? `${name} (iteration)` : name;
115-
}

packages/cursorless-org-docs/src/docs/user/languages/components/ScopeVisualizer.tsx

Lines changed: 129 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,117 @@
1-
import { Range } from "@cursorless/common";
1+
import {
2+
Range,
3+
type ScopeSupportFacet,
4+
type ScopeSupportFacetInfo,
5+
type TextualScopeSupportFacet,
6+
} from "@cursorless/common";
27
import React, { useState } from "react";
38
import scopeTestsJson from "../../../../../static/scopeTests.json";
49
import { Code, type Highlight } from "./Code";
510
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;
619

720
type RangeType = "content" | "removal";
821

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+
}
1033

1134
interface Props {
1235
languageId: string;
1336
}
1437

1538
export function ScopeVisualizer({ languageId }: Props) {
16-
const [fixtures] = useState(getFixtures(languageId));
39+
const [scopes] = useState(getScopeFixtures(languageId));
1740
const [rangeType, setRangeType] = useState<RangeType>("content");
1841
const [renderWhitespace, setRenderWhitespace] = useState(false);
1942

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+
) {
2083
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),
4188
)}
4289
</div>
4390
);
4491
}
4592

46-
function renderFixture(
93+
function renderFacet(
4794
languageId: string,
4895
rangeType: RangeType,
4996
renderWhitespace: boolean,
50-
fixture: Fixture,
97+
facet: Facet,
5198
) {
52-
const highlights = getHighlights(fixture, rangeType);
5399
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+
))}
63115
</div>
64116
);
65117
}
@@ -138,9 +190,49 @@ function getOverlap(a: Range, b: Range): Range | null {
138190
: null;
139191
}
140192

141-
function getFixtures(languageId: string): Fixture[] {
193+
function getScopeFixtures(languageId: string): Scope[] {
142194
const languageIds = new Set<string>(
143195
scopeTests.imports[languageId] ?? [languageId],
144196
);
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+
});
146238
}

packages/cursorless-org-docs/src/docs/user/languages/components/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import type {
2+
ScopeSupportFacet,
3+
TextualScopeSupportFacet,
4+
} from "@cursorless/common";
5+
16
export interface ScopeTestsJson {
27
imports: Record<string, string[]>;
38
fixtures: Fixture[];
49
}
510

611
export interface Fixture {
712
name: string;
8-
facet: string;
13+
facet: ScopeSupportFacet | TextualScopeSupportFacet;
914
languageId: string;
1015
code: string;
1116
scopes: Scope[];
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
camelCaseToAllDown,
3+
capitalize,
4+
scopeSupportFacetInfos,
5+
textualScopeSupportFacetInfos,
6+
type ScopeSupportFacet,
7+
type ScopeSupportFacetInfo,
8+
type ScopeType,
9+
type SimpleScopeTypeType,
10+
type TextualScopeSupportFacet,
11+
} from "@cursorless/common";
12+
13+
export function prettifyFacet(
14+
facet: ScopeSupportFacet | TextualScopeSupportFacet,
15+
keepScopeType: boolean,
16+
): string {
17+
let parts = facet.split(".").map(camelCaseToAllDown);
18+
if (parts.length === 1) {
19+
return capitalize(parts[0]);
20+
}
21+
const iterationIndex = parts.indexOf("iteration");
22+
let iterationParts: string[] = [];
23+
if (iterationIndex > 0) {
24+
iterationParts = parts.slice(iterationIndex);
25+
parts = parts.slice(0, iterationIndex);
26+
parts.length = iterationIndex;
27+
}
28+
const scopeName = parts.shift()!;
29+
let name = capitalize(parts.join(" "));
30+
if (keepScopeType) {
31+
name = capitalize(scopeName) + (parts.length > 0 ? ": " : " ") + name;
32+
}
33+
if (iterationParts.length > 0) {
34+
name += ` (${iterationParts.join(" ")})`;
35+
}
36+
return name;
37+
}
38+
39+
export function serializeScopeType(
40+
scopeType: SimpleScopeTypeType | ScopeType,
41+
): string {
42+
if (typeof scopeType === "string") {
43+
return scopeType;
44+
}
45+
return scopeType.type;
46+
}
47+
48+
export function prettifyScopeType(scopeType: string): string {
49+
return capitalize(camelCaseToAllDown(scopeType));
50+
}
51+
52+
export function getFacetInfo(
53+
languageId: string,
54+
facetId: ScopeSupportFacet | TextualScopeSupportFacet,
55+
): ScopeSupportFacetInfo {
56+
const facetInfo =
57+
languageId === "textual"
58+
? textualScopeSupportFacetInfos[facetId as TextualScopeSupportFacet]
59+
: scopeSupportFacetInfos[facetId as ScopeSupportFacet];
60+
61+
if (facetInfo == null) {
62+
throw Error(`Missing scope support facet info for: ${facetId}`);
63+
}
64+
65+
return facetInfo;
66+
}

0 commit comments

Comments
 (0)