Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
},

"ignorePatterns": [
"/packages/app-web-docs/src/docs/contributing/private-scopes/*.mdx",
"/packages/app-web-docs/src/docs/user/actions/*.mdx",
"/packages/app-web-docs/src/docs/user/languages/*.mdx",
"/packages/app-web-docs/src/docs/user/modifiers/*.mdx",
"/packages/app-web-docs/src/docs/user/scopes/*.mdx",
"/packages/app-web-docs/src/docs/user/languages/*.mdx",
"/packages/app-web-docs/src/docs/user/tutorial/*.mdx",
"/packages/app-web-docs/src/docs/contributing/private-scopes/*.mdx",
"/packages/app-web-docs/src/docs/**/fixtures/*.json",
"/packages/app-vscode/src/keyboard/grammar/generated/",
"/packages/lib-engine/src/customCommandGrammar/generated/",
"/packages/lib-engine/src/snippets/vendor/",
Expand Down
6 changes: 1 addition & 5 deletions packages/app-web-docs/docusaurus.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,7 @@ const config: Config = {
],
],

plugins: [
"docusaurus-plugin-sass",
"./src/plugins/recorded-tests-plugin.ts",
"./src/plugins/scope-tests-plugin.ts",
],
plugins: ["docusaurus-plugin-sass"],

themeConfig: {
navbar: {
Expand Down
19 changes: 3 additions & 16 deletions packages/app-web-docs/src/docs/components/Code.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,8 @@
background-color: #e5c02c;
}

/* Code hat referenced */
/* Code mark referenced */

.code-hat-referenced::before {
animation: code-hat-referenced-pulse 2s ease-in-out infinite;
}

@keyframes code-hat-referenced-pulse {
50% {
background-color: var(--code-hat-referenced-color);
}
}

@media (prefers-reduced-motion: reduce) {
.code-hat-referenced::before {
background-color: var(--code-hat-referenced-color);
animation: none;
}
.code-mark {
background-color: var(--code-mark-color);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { usePluginData } from "@docusaurus/useGlobalData";
import type { Dispatch, JSX, ReactNode, SetStateAction } from "react";
import { createContext, useContext, useMemo, useState } from "react";
import type { DecorationItem } from "shiki";
import type {
Position,
Range,
Selection,
TestCaseSnapshot,
} from "@cursorless/lib-common";
Expand All @@ -25,14 +25,15 @@ const RecordedTestVisualizerContext = createContext<
RecordedTestVisualizerContextValue | undefined
>(undefined);

interface ProviderProps {
children: ReactNode;
recordedTests: RecordedTest[];
}

export function RecordedTestVisualizerProvider({
children,
}: {
children: ReactNode;
}) {
const recordedTests = usePluginData(
"recorded-tests-plugin",
) as RecordedTest[];
recordedTests,
}: ProviderProps): JSX.Element {
const [renderWhitespace, setRenderWhitespace] = useState(true);
const fixtures = useMemo(
() =>
Expand All @@ -57,7 +58,7 @@ export function RecordedTestVisualizerProvider({
);
}

export function RecordedTestVisualizerOptions() {
export function RecordedTestVisualizerOptions(): JSX.Element {
const { renderWhitespace, setRenderWhitespace } = useRecordedTestVisualizer();

return (
Expand Down Expand Up @@ -99,7 +100,7 @@ export function RecordedTestVisualizer({
// oxlint-disable-next-line react_perf/jsx-no-new-object-as-prop
const link = {
name: "GitHub",
url: `https://github.com/cursorless-dev/cursorless/blob/main/resources/fixtures/recorded/docs/${path}`,
url: `https://github.com/cursorless-dev/cursorless/blob/main/resources/fixtures/recorded/${path}`,
};

const renderHats =
Expand All @@ -109,7 +110,7 @@ export function RecordedTestVisualizer({
return (
<div className="row">
<div className="col">
Input
Before
<CodeState
renderWhitespace={renderWhitespace}
renderHats={renderHats}
Expand All @@ -119,7 +120,7 @@ export function RecordedTestVisualizer({
/>
</div>
<div className="col">
Output
After
<CodeState
renderWhitespace={renderWhitespace}
renderHats={renderHats}
Expand Down Expand Up @@ -204,16 +205,21 @@ function toDecorations(
plainObjectToRange(hatRange),
)
: [];
const markRanges = renderHats
? Object.values(state.marks ?? {}).map(plainObjectToRange)
: [];

// Shiki rejects intersecting decorations. A zero-width cursor at the end of
// a hat range intersects the hat decoration, so render both on one wrapper.
// We only merge at the end because code-cursor-after recreates that exact
// boundary without competing with the hat's ::before pseudo-element.
// a hat or mark range intersects that decoration, so render both on one
// wrapper. We only merge at the end because code-cursor-after recreates that
// exact boundary without competing with the hat's ::before pseudo-element.
const mergedCursorPositions = selections
.filter(
(selection) =>
selection.isEmpty &&
hatRanges.some(({ end }) => end.isEqual(selection.active)),
[...hatRanges, ...markRanges].some(({ end }) =>
end.isEqual(selection.active),
),
)
.map(({ active }) => active);

Expand All @@ -228,10 +234,46 @@ function toDecorations(
),
)
.map(toDecoration),
...toMarkDecorations(markRanges, hatRanges, mergedCursorPositions),
...toHatDecorations(state, renderHats, mergedCursorPositions),
];
}

function toMarkDecorations(
markRanges: readonly Range[],
hatRanges: readonly Range[],
mergedCursorPositions: readonly Position[],
): DecorationItem[] {
return markRanges
.filter(
(range, index) =>
markRanges.findIndex((otherRange) => otherRange.isRangeEqual(range)) ===
index,
)
.map((range) => {
const className = ["code-mark"];

// Prefer placing the cursor on the smaller hat wrapper when both the hat
// and its containing mark end at the cursor position.
if (
mergedCursorPositions.some((position) => position.isEqual(range.end)) &&
!hatRanges.some(({ end }) => end.isEqual(range.end))
) {
className.push("code-cursor-after");
}

return {
start: range.start,
end: range.end,
alwaysWrap: true,
properties: {
className,
style: `--code-mark-color: ${highlightColors.content.background};`,
},
};
});
}

function toHatDecorations(
state: TestCaseSnapshot,
renderHats: boolean,
Expand All @@ -241,23 +283,12 @@ function toHatDecorations(
return [];
}

const markRanges = Object.values(state.marks ?? {}).map(plainObjectToRange);

return state.hatTokenMap.map(({ hatStyle, hatRange }) => {
const range = plainObjectToRange(hatRange);
const properties: DecorationItem["properties"] = {
className: ["code-hat", `code-hat-${hatStyle}`],
};

const isReferenced = markRanges.some((markRange) =>
markRange.contains(range),
);

if (isReferenced) {
properties.className?.push("code-hat-referenced");
properties.style = `--code-hat-referenced-color: ${highlightColors.content.background};`;
}

if (mergedCursorPositions.some((position) => position.isEqual(range.end))) {
// The hat uses ::before and the cursor uses ::after, allowing both
// visuals to share this wrapper without overlapping Shiki decorations.
Expand Down
23 changes: 16 additions & 7 deletions packages/app-web-docs/src/docs/components/ScopeVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { usePluginData } from "@docusaurus/useGlobalData";
import type { Dispatch, ReactNode, SetStateAction } from "react";
import type { Dispatch, JSX, ReactNode, SetStateAction } from "react";
import { createContext, useContext, useMemo, useState } from "react";
import { generateDecorations } from "./calculateHighlights";
import { Code } from "./Code";
Expand All @@ -18,8 +17,15 @@ const ScopeVisualizerContext = createContext<
ScopeVisualizerContextValue | undefined
>(undefined);

export function ScopeVisualizerProvider({ children }: { children: ReactNode }) {
const scopeTests = usePluginData("scope-tests-plugin") as Fixture[];
interface ProviderProps {
children: ReactNode;
scopeTests: Fixture[];
}

export function ScopeVisualizerProvider({
children,
scopeTests,
}: ProviderProps): JSX.Element {
const [rangeType, setRangeType] = useState<RangeType>("content");
const [renderWhitespace, setRenderWhitespace] = useState(true);
const fixtures = useMemo(
Expand All @@ -44,7 +50,7 @@ export function ScopeVisualizerProvider({ children }: { children: ReactNode }) {
);
}

export function ScopeVisualizerOptions() {
export function ScopeVisualizerOptions(): JSX.Element {
const { rangeType, renderWhitespace, setRangeType, setRenderWhitespace } =
useScopeVisualizer();

Expand Down Expand Up @@ -75,12 +81,15 @@ export function ScopeVisualizerOptions() {
);
}

interface ScopeProps {
interface ScopeVisualizerProps {
fixtureName: string;
languageId?: string;
}

export function ScopeVisualizer({ fixtureName, languageId }: ScopeProps) {
export function ScopeVisualizer({
fixtureName,
languageId,
}: ScopeVisualizerProps): JSX.Element {
const { fixtures, rangeType, renderWhitespace } = useScopeVisualizer();
const fixture = fixtures.get(fixtureName);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Private scopes

## Private

These scopes are not intended for public use and may change or be removed without notice.

- [`"access"`](./fieldAccess.mdx) - Field access.
- [`"parse tree string"`](./string.mdx) - String.
- [`"text fragment"`](./textFragment.mdx) - Text fragment.
- [`"disqualify delimiter"`](./disqualifyDelimiter.mdx) - Disqualify delimiter.
- [`"pair delimiter"`](./pairDelimiter.mdx) - Pair delimiter.
- [`"interior"`](./interior.mdx) - Interior.
- [Surrounding pair interior](./surroundingPairInterior.mdx)
- [Custom regex](./customRegex.mdx)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sidebar_label: Disqualify delimiter
---

import { ScopeVisualizer, ScopeVisualizerOptions, ScopeVisualizerProvider } from "@site/src/docs/components/ScopeVisualizer";
import scopeTests from "./fixtures/disqualifyDelimiter.json";

# Disqualify delimiter

Expand All @@ -22,7 +23,7 @@ Default: `disqualify delimiter`

## Visualization

<ScopeVisualizerProvider>
<ScopeVisualizerProvider scopeTests={scopeTests}>

The following are internal scopes. They are not intended for user interaction or spoken use. These scopes exist solely for internal Cursorless functionality.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ sidebar_label: Access
---

import { ScopeVisualizer, ScopeVisualizerOptions, ScopeVisualizerProvider } from "@site/src/docs/components/ScopeVisualizer";
import scopeTests from "./fixtures/fieldAccess.json";

# Access (Field access)

Expand All @@ -22,7 +23,7 @@ Default: `access`

## Visualization

<ScopeVisualizerProvider>
<ScopeVisualizerProvider scopeTests={scopeTests}>

The following are internal scopes. They are not intended for user interaction or spoken use. These scopes exist solely for internal Cursorless functionality.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading
Loading