Skip to content

Commit 178d905

Browse files
Remove old scope support
1 parent a610bd6 commit 178d905

File tree

5 files changed

+230
-423
lines changed

5 files changed

+230
-423
lines changed

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React from "react";
22
import { ScopeSupport } from "./ScopeSupport";
3-
import { ScopeVisualizer } from "./ScopeVisualizer";
43

54
interface Props {
65
languageId: string;
@@ -9,10 +8,6 @@ interface Props {
98
export function Language({ languageId }: Props) {
109
return (
1110
<>
12-
<h2>Scopes</h2>
13-
14-
<ScopeVisualizer languageId={languageId} />
15-
1611
<ScopeSupport languageId={languageId} />
1712
</>
1813
);
Lines changed: 230 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,247 @@
11
import {
2-
ScopeSupportFacetLevel,
3-
languageScopeSupport,
4-
plaintextScopeSupportFacetInfos,
5-
scopeSupportFacetInfos,
6-
scopeSupportFacets,
2+
Range,
3+
serializeScopeType,
74
type PlaintextScopeSupportFacet,
85
type ScopeSupportFacet,
6+
type ScopeSupportFacetInfo,
97
} from "@cursorless/common";
10-
import * as React from "react";
11-
import {
12-
ScopeSupportForLevel,
13-
type FacetWrapper,
14-
} from "./ScopeSupportForLevel";
8+
import React, { useState } from "react";
9+
import { Code, type Highlight } from "./Code";
10+
import "./ScopeSupport.css";
11+
import type { Fixture, ScopeTests } from "./types";
12+
import { getFacetInfo, prettifyFacet, prettifyScopeType } from "./util";
13+
import { usePluginData } from "@docusaurus/useGlobalData";
14+
15+
type RangeType = "content" | "removal";
16+
17+
interface Scope {
18+
scope: string;
19+
facets: Facet[];
20+
}
21+
22+
interface Facet {
23+
facet: ScopeSupportFacet | PlaintextScopeSupportFacet;
24+
name: string;
25+
info: ScopeSupportFacetInfo;
26+
fixtures: Fixture[];
27+
}
1528

1629
interface Props {
1730
languageId: string;
1831
}
1932

20-
function getSupport(languageId: string): FacetWrapper[] {
21-
if (languageId === "plaintext") {
22-
return Object.keys(plaintextScopeSupportFacetInfos)
23-
.sort()
24-
.map((f) => {
25-
const facet = f as PlaintextScopeSupportFacet;
26-
return {
27-
facet,
28-
supportLevel: ScopeSupportFacetLevel.supported,
29-
info: plaintextScopeSupportFacetInfos[facet],
30-
};
33+
export function ScopeSupport({ languageId }: Props) {
34+
const scopeTests = usePluginData("scope-tests-plugin") as ScopeTests;
35+
const [scopes] = useState(getScopeFixtures(scopeTests, languageId));
36+
const [rangeType, setRangeType] = useState<RangeType>("content");
37+
const [renderWhitespace, setRenderWhitespace] = useState(false);
38+
39+
const renderOptions = () => {
40+
return (
41+
<div className="mb-4">
42+
<select
43+
value={rangeType}
44+
onChange={(e) => setRangeType(e.target.value as RangeType)}
45+
>
46+
<option value="content">Content range</option>
47+
<option value="removal">Removal range</option>
48+
</select>
49+
50+
<label className="ml-1">
51+
<input
52+
type="checkbox"
53+
checked={renderWhitespace}
54+
onChange={(e) => setRenderWhitespace(e.target.checked)}
55+
/>
56+
Render whitespace
57+
</label>
58+
</div>
59+
);
60+
};
61+
62+
return (
63+
<>
64+
<h2>Scopes</h2>
65+
66+
<p>
67+
Below are visualizations of all our scope tests for this language. These
68+
were designed primarily as tests rather than documentation. There are
69+
quite a few of them, and they may be a bit overwhelming from a
70+
documentation perspective.
71+
</p>
72+
73+
{renderOptions()}
74+
75+
{scopes.map((scope) =>
76+
renderScope(languageId, rangeType, renderWhitespace, scope),
77+
)}
78+
</>
79+
);
80+
}
81+
82+
function renderScope(
83+
languageId: string,
84+
rangeType: RangeType,
85+
renderWhitespace: boolean,
86+
scope: Scope,
87+
) {
88+
const href = scope.scope.toLowerCase().replaceAll(" ", "-");
89+
return (
90+
<div key={scope.scope}>
91+
<h3 id={href}>
92+
{scope.scope}
93+
<a href={`#${href}`} className="link-icon">
94+
🔗
95+
</a>
96+
</h3>
97+
{scope.facets.map((f) =>
98+
renderFacet(languageId, rangeType, renderWhitespace, f),
99+
)}
100+
</div>
101+
);
102+
}
103+
104+
function renderFacet(
105+
languageId: string,
106+
rangeType: RangeType,
107+
renderWhitespace: boolean,
108+
facet: Facet,
109+
) {
110+
return (
111+
<div key={facet.facet}>
112+
<span className="facet-name" title={facet.facet}>
113+
{facet.name}
114+
</span>
115+
<br />
116+
<i>{facet.info.description}</i>
117+
{facet.fixtures.map((fixture) => (
118+
<Code
119+
key={fixture.name}
120+
languageId={languageId}
121+
renderWhitespace={renderWhitespace}
122+
highlights={getHighlights(fixture, rangeType)}
123+
>
124+
{fixture.code}
125+
</Code>
126+
))}
127+
</div>
128+
);
129+
}
130+
131+
function getHighlights(fixture: Fixture, rangeType: RangeType): Highlight[] {
132+
const highlights: Highlight[] = [];
133+
const domainRanges: Range[] = [];
134+
135+
for (const scope of fixture.scopes) {
136+
const conciseRanges =
137+
rangeType === "content"
138+
? scope.targets.map((t) => t.content)
139+
: scope.targets.map((t) => t.removal ?? t.content);
140+
const ranges = conciseRanges.map((r) => Range.fromConcise(r));
141+
142+
if (scope.domain != null && !conciseRanges.includes(scope.domain)) {
143+
domainRanges.push(Range.fromConcise(scope.domain));
144+
}
145+
146+
for (const r of ranges) {
147+
let range = r;
148+
149+
const overlap = highlights
150+
.map((h) => getOverlap(h.range, range))
151+
.find((o) => o != null);
152+
153+
if (overlap != null) {
154+
highlights.push({
155+
type: rangeType,
156+
range: overlap,
157+
});
158+
range = new Range(overlap.end, range.end);
159+
}
160+
161+
highlights.push({
162+
type: rangeType,
163+
range,
31164
});
165+
}
32166
}
33-
const supportLevels = languageScopeSupport[languageId] ?? {};
34-
return [...scopeSupportFacets].sort().map((f) => {
35-
const facet = f as ScopeSupportFacet;
36-
return {
37-
facet,
38-
supportLevel: supportLevels[facet],
39-
info: scopeSupportFacetInfos[facet],
40-
};
41-
});
167+
168+
for (const range of domainRanges) {
169+
if (highlights.every((h) => !hasOverlap(h.range, range))) {
170+
highlights.push({
171+
type: "domain",
172+
range,
173+
});
174+
}
175+
}
176+
177+
if (
178+
highlights.some((h) => highlights.some((o) => hasOverlap(h.range, o.range)))
179+
) {
180+
console.error("Overlapping highlights detected:");
181+
console.error(fixture.name);
182+
console.error(highlights);
183+
}
184+
185+
return highlights;
42186
}
43187

44-
export function ScopeSupport({ languageId }: Props): React.JSX.Element {
45-
const facets = getSupport(languageId);
46-
const supportedFacets = facets.filter(
47-
(facet) => facet.supportLevel === ScopeSupportFacetLevel.supported,
48-
);
49-
const unsupportedFacets = facets.filter(
50-
(facet) => facet.supportLevel === ScopeSupportFacetLevel.unsupported,
188+
function hasOverlap(a: Range, b: Range): boolean {
189+
return getOverlap(a, b) != null;
190+
}
191+
192+
function getOverlap(a: Range, b: Range): Range | null {
193+
const intersection = a.intersection(b);
194+
return intersection != null &&
195+
!intersection.isEmpty &&
196+
!a.contains(b) &&
197+
!b.contains(a)
198+
? intersection
199+
: null;
200+
}
201+
202+
function getScopeFixtures(scopeTests: ScopeTests, languageId: string): Scope[] {
203+
const languageIds = new Set<string>(
204+
scopeTests.imports[languageId] ?? [languageId],
51205
);
52-
const unspecifiedFacets = facets.filter(
53-
(facet) => facet.supportLevel == null,
206+
const fixtures = scopeTests.fixtures.filter((f) =>
207+
languageIds.has(f.languageId),
54208
);
209+
const map: Record<string, Record<string, Facet>> = {};
55210

56-
return (
57-
<>
58-
<ScopeSupportForLevel
59-
facets={supportedFacets}
60-
title="Supported facets"
61-
subtitle="These scope facets are supported"
62-
/>
63-
64-
<ScopeSupportForLevel
65-
facets={unsupportedFacets}
66-
title="Unsupported facets"
67-
subtitle="These facets are not supported yet and needs a developer to implement them"
68-
description={
69-
<>
70-
We would happily accept{" "}
71-
<a href="https://www.cursorless.org/docs/contributing/adding-a-new-scope">
72-
contributions
73-
</a>
74-
</>
75-
}
76-
/>
77-
78-
<ScopeSupportForLevel
79-
facets={unspecifiedFacets}
80-
title="Unspecified facets"
81-
subtitle="These facets are unspecified"
82-
description={
83-
<>
84-
Note that in many instances we actually do support these scopes and
85-
facets, but we have not yet updated 'languageScopeSupport' to
86-
reflect this fact.
87-
<br />
88-
We would happily accept{" "}
89-
<a href="https://www.cursorless.org/docs/contributing/adding-a-new-scope">
90-
contributions
91-
</a>
92-
</>
93-
}
94-
/>
95-
</>
96-
);
211+
for (const fixture of fixtures) {
212+
const info = getFacetInfo(fixture.languageId, fixture.facet);
213+
let scope = serializeScopeType(info.scopeType);
214+
215+
if (scope.startsWith("private.")) {
216+
continue;
217+
}
218+
219+
scope = prettifyScopeType(scope);
220+
221+
const facet = prettifyFacet(fixture.facet, true);
222+
if (map[scope] == null) {
223+
map[scope] = {};
224+
}
225+
if (map[scope][fixture.facet] == null) {
226+
map[scope][fixture.facet] = {
227+
facet: fixture.facet,
228+
name: facet,
229+
info,
230+
fixtures: [],
231+
};
232+
}
233+
map[scope][fixture.facet].fixtures.push(fixture);
234+
}
235+
236+
return Object.keys(map)
237+
.sort()
238+
.map((scope): Scope => {
239+
const data = map[scope];
240+
const facets = Object.keys(data)
241+
.sort()
242+
.map((facet): Facet => {
243+
return data[facet];
244+
});
245+
return { scope, facets };
246+
});
97247
}

0 commit comments

Comments
 (0)