Skip to content

Commit 4bdaf08

Browse files
Docs visualization: Render hats and improve selection styling (#3329)
1 parent 5a6f97d commit 4bdaf08

6 files changed

Lines changed: 88 additions & 58 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 2026
44

5-
- 2026-09-01: Visualize modifiers on the documentation page. ([#3328](https://github.com/cursorless-dev/cursorless/pull/3328))
5+
- 2026-09-01: Visualize modifiers on the documentation page. ([#3328](https://github.com/cursorless-dev/cursorless/pull/3328); [#3329](https://github.com/cursorless-dev/cursorless/pull/3329))
66
- 2026-08-30: Overhauled the user documentation with generated and grouped action, modifier, and scope reference pages, richer descriptions and examples, dedicated paired-delimiter and target guides, and a streamlined landing page. ([#3303](https://github.com/cursorless-dev/cursorless/pull/3303); [#3304](https://github.com/cursorless-dev/cursorless/pull/3304); [#3305](https://github.com/cursorless-dev/cursorless/pull/3305); [#3306](https://github.com/cursorless-dev/cursorless/pull/3306); [#3307](https://github.com/cursorless-dev/cursorless/pull/3307); [#3308](https://github.com/cursorless-dev/cursorless/pull/3308); [#3309](https://github.com/cursorless-dev/cursorless/pull/3309); [#3310](https://github.com/cursorless-dev/cursorless/pull/3310); [#3311](https://github.com/cursorless-dev/cursorless/pull/3311); [#3312](https://github.com/cursorless-dev/cursorless/pull/3312); [#3313](https://github.com/cursorless-dev/cursorless/pull/3313); [#3314](https://github.com/cursorless-dev/cursorless/pull/3314); [#3315](https://github.com/cursorless-dev/cursorless/pull/3315); [#3316](https://github.com/cursorless-dev/cursorless/pull/3316))
77
- 2026-05-03: Add support for if and for statements in Talon-script. [`#3274`](https://github.com/cursorless-dev/cursorless/pull/3274)
88
- 2026-03-12: Added `userColor3` and `userColor4` as additional user-configurable hat colors. They are disabled by default until you enable and name them. ([#3206](https://github.com/cursorless-dev/cursorless/pull/3206))

‎packages/app-web-docs/src/docs/components/Code.css‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@
7272
}
7373
}
7474

75+
/* Code selection */
76+
.code-selection {
77+
background-color: #264f78;
78+
}
79+
80+
/* Code cursor */
81+
7582
.code-cursor-before,
7683
.code-cursor-after {
7784
position: relative;
@@ -91,12 +98,12 @@
9198

9299
.code-cursor-before::before {
93100
left: -1px;
94-
border-left: 2px solid white;
101+
border-left: 2px solid #aeafad;
95102
}
96103

97104
.code-cursor-after::after {
98105
right: -1px;
99-
border-right: 2px solid white;
106+
border-right: 2px solid #aeafad;
100107
}
101108

102109
@keyframes code-cursor-blink {
@@ -111,3 +118,22 @@
111118
animation: none;
112119
}
113120
}
121+
122+
/* Code hat */
123+
124+
.code-hat {
125+
position: relative;
126+
}
127+
128+
.code-hat-default::before {
129+
content: "";
130+
position: absolute;
131+
top: 0.15em;
132+
left: 50%;
133+
width: 0.3em;
134+
height: 0.3em;
135+
border-radius: 50%;
136+
background-color: #b9b6cd;
137+
transform: translate(-50%, -100%);
138+
pointer-events: none;
139+
}

‎packages/app-web-docs/src/docs/components/RecordedTestVisualizer.tsx‎

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import { createContext, useContext, useMemo, useState } from "react";
44
import type { DecorationItem } from "shiki";
55
import type {
66
SelectionPlainObject,
7+
SerializedMarks,
78
TestCaseSnapshot,
89
} from "@cursorless/lib-common";
9-
import { BorderStyle, plainObjectToSelection } from "@cursorless/lib-common";
10+
import { plainObjectToSelection, splitKey } from "@cursorless/lib-common";
1011
import { Code } from "./Code";
11-
import { highlightColors } from "./highlightColors";
12-
import { highlightToDecoration } from "./highlightsToDecorations";
1312
import type { RecordedTest } from "./types";
1413

1514
interface RecordedTestVisualizerContextValue {
@@ -72,11 +71,13 @@ export function RecordedTestVisualizerOptions() {
7271
);
7372
}
7473

75-
interface ModifierProps {
74+
interface RecordedTestVisualizerProps {
7675
fixtureName: string;
7776
}
7877

79-
export function RecordedTestVisualizer({ fixtureName }: ModifierProps) {
78+
export function RecordedTestVisualizer({
79+
fixtureName,
80+
}: RecordedTestVisualizerProps): JSX.Element {
8081
const { fixtures, renderWhitespace } = useRecordedTestVisualizer();
8182
const test = fixtures.get(fixtureName);
8283

@@ -121,27 +122,35 @@ export function RecordedTestVisualizer({ fixtureName }: ModifierProps) {
121122
);
122123
}
123124

124-
function CodeState({
125-
renderWhitespace,
126-
languageId,
127-
link,
128-
state,
129-
}: {
125+
interface CodeStateProps {
130126
renderWhitespace: boolean;
131127
languageId: string;
132128
link: {
133129
name: string;
134130
url: string;
135131
};
136132
state: TestCaseSnapshot;
137-
}): JSX.Element {
133+
}
134+
135+
function CodeState({
136+
renderWhitespace,
137+
languageId,
138+
link,
139+
state,
140+
}: CodeStateProps): JSX.Element {
141+
const decorations = useMemo(
142+
() => [
143+
...state.selections.map(toDecoration),
144+
...toMarkDecorations(state.marks),
145+
],
146+
[state.selections, state.marks],
147+
);
138148
return (
139149
<Code
140150
link={link}
141151
languageId={languageId}
142152
renderWhitespace={renderWhitespace}
143-
// oxlint-disable-next-line react-perf/jsx-no-new-array-as-prop
144-
decorations={state.selections.map(toDecoration)}
153+
decorations={decorations}
145154
>
146155
{state.documentContents}
147156
</Code>
@@ -151,50 +160,45 @@ function CodeState({
151160
function toDecoration(plainSelection: SelectionPlainObject): DecorationItem {
152161
const selection = plainObjectToSelection(plainSelection);
153162

154-
if (selection.isEmpty) {
155-
return {
156-
start: selection.start,
157-
end: selection.start,
158-
properties: {
159-
className: ["code-cursor-before"],
160-
},
161-
};
162-
}
163-
164-
const decoration = highlightToDecoration({
165-
range: selection,
166-
style: {
167-
backgroundColor: highlightColors.content.background,
168-
borderColorSolid: highlightColors.content.borderSolid,
169-
borderColorPorous: highlightColors.content.borderPorous,
170-
borderStyle: {
171-
top: BorderStyle.solid,
172-
bottom: BorderStyle.solid,
173-
left: BorderStyle.solid,
174-
right: BorderStyle.solid,
175-
},
176-
borderRadius: {
177-
topLeft: true,
178-
topRight: true,
179-
bottomRight: true,
180-
bottomLeft: true,
181-
},
182-
},
183-
});
184-
185-
const className = selection.isReversed
163+
const cursorClassName = selection.isReversed
186164
? "code-cursor-before"
187165
: "code-cursor-after";
188166

167+
const classNames = selection.isEmpty
168+
? [cursorClassName]
169+
: [cursorClassName, "code-selection"];
170+
189171
return {
190-
...decoration,
172+
start: selection.start,
173+
end: selection.end,
174+
alwaysWrap: true,
191175
properties: {
192-
...decoration.properties,
193-
className: [className],
176+
className: classNames,
194177
},
195178
};
196179
}
197180

181+
function toMarkDecorations(
182+
marks: SerializedMarks | undefined,
183+
): DecorationItem[] {
184+
if (marks == null) {
185+
return [];
186+
}
187+
188+
return Object.entries(marks).map(([key, range]) => {
189+
const { hatStyle } = splitKey(key);
190+
191+
return {
192+
start: range.start,
193+
end: range.end,
194+
alwaysWrap: true,
195+
properties: {
196+
className: ["code-hat", `code-hat-${hatStyle}`],
197+
},
198+
};
199+
});
200+
}
201+
198202
function useRecordedTestVisualizer(): RecordedTestVisualizerContextValue {
199203
const value = useContext(RecordedTestVisualizerContext);
200204
if (value == null) {

‎resources/fixtures/recorded/docs/modifiers/inferPreviousMark.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ command:
2121
usePrePhraseSnapshot: false
2222
initialState:
2323
documentContents: |-
24-
// Hello world
24+
2525
const aaa = 0;
2626
selections:
2727
- anchor: {line: 0, character: 0}
@@ -32,7 +32,7 @@ initialState:
3232
end: {line: 1, character: 9}
3333
finalState:
3434
documentContents: |-
35-
// Hello world
35+
3636
const aaa = 0;
3737
selections:
3838
- anchor: {line: 1, character: 6}

‎resources/fixtures/recorded/docs/modifiers/inferPreviousMark2.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ command:
1919
usePrePhraseSnapshot: false
2020
initialState:
2121
documentContents: |-
22-
// Hello world
22+
2323
const aaa = 0;
2424
selections:
2525
- anchor: {line: 0, character: 0}
@@ -30,7 +30,7 @@ initialState:
3030
end: {line: 1, character: 9}
3131
finalState:
3232
documentContents: |-
33-
// Hello world
33+
3434
aaa
3535
selections:
3636
- anchor: {line: 0, character: 0}

‎resources/fixtures/recorded/docs/modifiers/inferPreviousMark3.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ command:
1919
usePrePhraseSnapshot: false
2020
initialState:
2121
documentContents: |-
22-
// Hello world
22+
2323
const aaa = 0;
2424
selections:
2525
- anchor: {line: 0, character: 0}
@@ -30,7 +30,7 @@ initialState:
3030
end: {line: 1, character: 9}
3131
finalState:
3232
documentContents: |-
33-
// Hello world
33+
3434
const aaa = aaa;
3535
selections:
3636
- anchor: {line: 0, character: 0}

0 commit comments

Comments
 (0)