cursedbelt 5.79.4 → 5.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/react/activity-bar/ActivityBar.d.ts +18 -1
  2. package/dist/react/activity-bar/ActivityBar.d.ts.map +1 -1
  3. package/dist/react/activity-bar/ActivityBar.js +34 -5
  4. package/dist/react/activity-bar/ActivityBar.js.map +1 -1
  5. package/dist/react/activity-bar/activityBar.css +20 -0
  6. package/dist/react/code-editor/CodeEditor.d.ts +5 -1
  7. package/dist/react/code-editor/CodeEditor.d.ts.map +1 -1
  8. package/dist/react/code-editor/CodeEditor.js +13 -6
  9. package/dist/react/code-editor/CodeEditor.js.map +1 -1
  10. package/dist/react/code-editor/keys.d.ts +8 -0
  11. package/dist/react/code-editor/keys.d.ts.map +1 -0
  12. package/dist/react/code-editor/keys.js +16 -0
  13. package/dist/react/code-editor/keys.js.map +1 -0
  14. package/dist/react/code-editor/languages.d.ts.map +1 -1
  15. package/dist/react/code-editor/languages.js +4 -2
  16. package/dist/react/code-editor/languages.js.map +1 -1
  17. package/dist/react/code-editor/lineAbove.d.ts +5 -4
  18. package/dist/react/code-editor/lineAbove.d.ts.map +1 -1
  19. package/dist/react/code-editor/lineAbove.js +6 -10
  20. package/dist/react/code-editor/lineAbove.js.map +1 -1
  21. package/dist/react/code-editor/markdownRules.d.ts +8 -0
  22. package/dist/react/code-editor/markdownRules.d.ts.map +1 -0
  23. package/dist/react/code-editor/markdownRules.js +64 -0
  24. package/dist/react/code-editor/markdownRules.js.map +1 -0
  25. package/dist/react/code-editor/occurrences.d.ts +8 -0
  26. package/dist/react/code-editor/occurrences.d.ts.map +1 -0
  27. package/dist/react/code-editor/occurrences.js +63 -0
  28. package/dist/react/code-editor/occurrences.js.map +1 -0
  29. package/dist/react/code-editor/plainInput.d.ts +4 -0
  30. package/dist/react/code-editor/plainInput.d.ts.map +1 -0
  31. package/dist/react/code-editor/plainInput.js +36 -0
  32. package/dist/react/code-editor/plainInput.js.map +1 -0
  33. package/dist/react/code-editor/theme.d.ts.map +1 -1
  34. package/dist/react/code-editor/theme.js +5 -0
  35. package/dist/react/code-editor/theme.js.map +1 -1
  36. package/dist/react/line-keys/lineKeys.d.ts +12 -8
  37. package/dist/react/line-keys/lineKeys.d.ts.map +1 -1
  38. package/dist/react/line-keys/lineKeys.js +14 -10
  39. package/dist/react/line-keys/lineKeys.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/react/activity-bar/ActivityBar.spec.tsx +46 -0
  42. package/src/react/activity-bar/ActivityBar.tsx +67 -4
  43. package/src/react/activity-bar/activityBar.css +20 -0
  44. package/src/react/code-editor/CodeEditor.tsx +24 -7
  45. package/src/react/code-editor/keys.spec.ts +61 -0
  46. package/src/react/code-editor/keys.ts +17 -0
  47. package/src/react/code-editor/languages.ts +4 -2
  48. package/src/react/code-editor/lineAbove.spec.ts +1 -16
  49. package/src/react/code-editor/lineAbove.ts +6 -10
  50. package/src/react/code-editor/markdownRules.spec.ts +26 -0
  51. package/src/react/code-editor/markdownRules.ts +73 -0
  52. package/src/react/code-editor/occurrences.ts +69 -0
  53. package/src/react/code-editor/plainInput.spec.ts +23 -0
  54. package/src/react/code-editor/plainInput.ts +35 -0
  55. package/src/react/code-editor/theme.ts +5 -0
  56. package/src/react/line-keys/lineKeys.spec.tsx +19 -7
  57. package/src/react/line-keys/lineKeys.ts +14 -10
@@ -0,0 +1,63 @@
1
+ import { EditorSelection } from '@codemirror/state';
2
+ /**
3
+ * VS Code's ⌘D and ⇧⌘L — the two multi-cursor keys `defaultKeymap` leaves to `@codemirror/search`,
4
+ * which `CodeEditor` does not load (its find panel would fight the app's own find). Exact text,
5
+ * case-sensitive; a caret with nothing selected first takes the word it is in, as in VS Code.
6
+ */
7
+ /** The text the commands look for: the main selection, or the word under an empty caret. */
8
+ function queryOf(state) {
9
+ const main = state.selection.main;
10
+ const range = main.empty ? state.wordAt(main.head) : main;
11
+ if (!range || range.empty)
12
+ return null;
13
+ return { query: state.sliceDoc(range.from, range.to), range: EditorSelection.range(range.from, range.to) };
14
+ }
15
+ /** Where `query` starts in `text`, matches never overlapping. */
16
+ function matchesOf(text, query) {
17
+ const starts = [];
18
+ for (let at = text.indexOf(query); at !== -1; at = text.indexOf(query, at + query.length))
19
+ starts.push(at);
20
+ return starts;
21
+ }
22
+ function isSelected(state, from, to) {
23
+ return state.selection.ranges.some((r) => r.from === from && r.to === to);
24
+ }
25
+ /** ⌘D — select the word under the caret, then add the next match of it, wrapping at the end. */
26
+ export const selectNextOccurrence = ({ state, dispatch }) => {
27
+ const found = queryOf(state);
28
+ if (!found)
29
+ return false;
30
+ const { query, range } = found;
31
+ if (state.selection.main.empty) {
32
+ dispatch(state.update({ selection: state.selection.replaceRange(range), scrollIntoView: true, userEvent: 'select' }));
33
+ return true;
34
+ }
35
+ // Every match, the ones after the selection first, then from the top — the first not yet selected.
36
+ const starts = matchesOf(state.doc.toString(), query);
37
+ const at = [...starts.filter((a) => a >= range.to), ...starts.filter((a) => a < range.to)].find((a) => !isSelected(state, a, a + query.length));
38
+ if (at === undefined)
39
+ return false;
40
+ dispatch(state.update({
41
+ selection: state.selection.addRange(EditorSelection.range(at, at + query.length)),
42
+ scrollIntoView: true,
43
+ userEvent: 'select',
44
+ }));
45
+ return true;
46
+ };
47
+ /** ⇧⌘L — every match of the selection (or the word under the caret) selected at once. */
48
+ export const selectAllOccurrences = ({ state, dispatch }) => {
49
+ const found = queryOf(state);
50
+ if (!found)
51
+ return false;
52
+ const { query, range } = found;
53
+ const starts = matchesOf(state.doc.toString(), query);
54
+ const ranges = starts.map((at) => EditorSelection.range(at, at + query.length));
55
+ const main = Math.max(0, starts.indexOf(range.from));
56
+ dispatch(state.update({ selection: EditorSelection.create(ranges, main), userEvent: 'select' }));
57
+ return true;
58
+ };
59
+ export const occurrenceBindings = [
60
+ { key: 'Mod-d', run: selectNextOccurrence, preventDefault: true },
61
+ { key: 'Shift-Mod-l', run: selectAllOccurrences, preventDefault: true },
62
+ ];
63
+ //# sourceMappingURL=occurrences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"occurrences.js","sourceRoot":"","sources":["../../../src/react/code-editor/occurrences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA4D,MAAM,mBAAmB,CAAC;AAG9G;;;;GAIG;AAEH,4FAA4F;AAC5F,SAAS,OAAO,CAAC,KAAkB;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1D,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AAC7G,CAAC;AAED,iEAAiE;AACjE,SAAS,SAAS,CAAC,IAAY,EAAE,KAAa;IAC5C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3G,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,KAAkB,EAAE,IAAY,EAAE,EAAU;IAC9D,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,oBAAoB,GAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC/B,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QACtH,OAAO,IAAI,CAAC;IACd,CAAC;IACD,mGAAmG;IACnG,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAC7F,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAC/C,CAAC;IACF,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACnC,QAAQ,CACN,KAAK,CAAC,MAAM,CAAC;QACX,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QACjF,cAAc,EAAE,IAAI;QACpB,SAAS,EAAE,QAAQ;KACpB,CAAC,CACH,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,yFAAyF;AACzF,MAAM,CAAC,MAAM,oBAAoB,GAAiB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE;IACxE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IAC/B,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAA0B;IACvD,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,oBAAoB,EAAE,cAAc,EAAE,IAAI,EAAE;IACjE,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,oBAAoB,EAAE,cAAc,EAAE,IAAI,EAAE;CACxE,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** The text the keys meant, or null when `text` replacing `replaced` is not a substitution. */
2
+ export declare function unsmarten(replaced: string, text: string): string | null;
3
+ export declare const plainInput: import("@codemirror/state").Extension;
4
+ //# sourceMappingURL=plainInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainInput.d.ts","sourceRoot":"","sources":["../../../src/react/code-editor/plainInput.ts"],"names":[],"mappings":"AAgBA,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKvE;AAED,eAAO,MAAM,UAAU,uCAUrB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { EditorView } from '@codemirror/view';
2
+ /**
3
+ * What the OS's "smart punctuation" types into a source editor, put back to what the keys said.
4
+ *
5
+ * iOS (and a Mac with Edit ▸ Substitutions on) rewrites `--` to `—` and `"` to `“` as you type —
6
+ * in markdown that eats a rule's dashes and a code span's quotes (the owner, 2026-10-14, forge
7
+ * task 3184: "when typing three dashes … it shifts and deletes some dashes … please fix all other
8
+ * syntax errors that may be similar"). `CodeEditor` already asks the browser for no autocorrect;
9
+ * this is the half that holds where the OS does not listen. VS Code substitutes nothing, either.
10
+ *
11
+ * Only a substitution is undone: a dash replacing the `-` (or `--`) the keys just typed, or a
12
+ * curly quote arriving where a straight one was pressed. A `—` pasted in is a paste, never seen here.
13
+ */
14
+ const QUOTES = { '“': '"', '”': '"', '‘': "'", '’': "'" };
15
+ /** The text the keys meant, or null when `text` replacing `replaced` is not a substitution. */
16
+ export function unsmarten(replaced, text) {
17
+ if ((text === '—' || text === '–') && /^-{1,2}$/.test(replaced))
18
+ return text === '—' && replaced === '-' ? '--' : `${replaced}-`;
19
+ const quote = QUOTES[text];
20
+ if (quote !== undefined && (replaced === '' || replaced === quote))
21
+ return quote;
22
+ return null;
23
+ }
24
+ export const plainInput = EditorView.inputHandler.of((view, from, to, text) => {
25
+ const meant = unsmarten(view.state.sliceDoc(from, to), text);
26
+ if (meant === null)
27
+ return false;
28
+ view.dispatch({
29
+ changes: { from, to, insert: meant },
30
+ selection: { anchor: from + meant.length },
31
+ scrollIntoView: true,
32
+ userEvent: 'input.type',
33
+ });
34
+ return true;
35
+ });
36
+ //# sourceMappingURL=plainInput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plainInput.js","sourceRoot":"","sources":["../../../src/react/code-editor/plainInput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,GAA2B,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAElF,+FAA+F;AAC/F,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAY;IACtD,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IACjI,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACjF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAC5E,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7D,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,CAAC,QAAQ,CAAC;QACZ,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QACpC,SAAS,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE;QAC1C,cAAc,EAAE,IAAI;QACpB,SAAS,EAAE,YAAY;KACxB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/react/code-editor/theme.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,uCAwIxB,CAAC;AAiEH,eAAO,MAAM,oBAAoB,uCAAuC,CAAC"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../../src/react/code-editor/theme.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,aAAa,uCA6IxB,CAAC;AAiEH,eAAO,MAAM,oBAAoB,uCAAuC,CAAC"}
@@ -20,6 +20,11 @@ export const cbEditorTheme = EditorView.theme({
20
20
  '.cm-content': {
21
21
  caretColor: 'var(--foreground)',
22
22
  padding: '0.5rem 0',
23
+ // Every character drawn as itself — VS Code's `editor.fontLigatures: false`. A ligature font
24
+ // (JetBrains Mono, desk's) joins `--` and `---` into one glyph that redraws as each dash
25
+ // lands, and turns `!=`, `=>` and `<=` into symbols that are not what was typed (forge task 3184).
26
+ fontVariantLigatures: 'none',
27
+ fontFeatureSettings: '"calt" 0, "liga" 0',
23
28
  },
24
29
  // CodeMirror's own recipe for a minimum height: give it to the content AND the gutter, so the
25
30
  // line-number rail runs the editor's full height instead of stopping under the last line
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/react/code-editor/theme.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../../src/react/code-editor/theme.ts"],"names":[],"mappings":"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"}
@@ -4,14 +4,14 @@
4
4
  * ⌘↵ (Ctrl↵) a new line BELOW the caret's line, from anywhere on it
5
5
  * ⇧⌘↵ (Ctrl⇧↵) a new line ABOVE it
6
6
  * ⇧⌘K (Ctrl⇧K) delete the line (every line the selection touches)
7
- * ⌘↑ ⌘↓ (Ctrl↑ Ctrl↓) move the line (every line the selection touches) up or down one
8
- * ⌥↑ ⌥↓ (Alt↑ Alt↓) the same — VS Code's own binding for it
7
+ * ⌥↑ ⌥↓ (Alt↑ Alt↓) move the line (every line the selection touches) up or down one
9
8
  *
10
9
  * The owner asked for these by name (2026-10-05, desk's context components first, "in apps with
11
- * multi line text boxes"; ⌘↑ / ⌘↓ the same day, in his words "like vscode does it"). ⌘↑ / ⌘↓ is
12
- * the owner's chord, and it costs the box the Mac's jump-to-top/bottom (⌘↑ is still that in a
13
- * single-line `<input>`, which this never touches); ⌥↑ / ⌥↓ is what VS Code itself binds, so
14
- * hands trained there find it too. They are one document listener rather than an `onKeyDown` on each
10
+ * multi line text boxes"). ⌘↑ / ⌘↓ is NOT a line key: it is VS Code's — and the Mac's — jump to the
11
+ * top or bottom, the line staying where it is (the owner, 2026-10-14, forge task 3182: "holding
12
+ * command and pushing up should jump to the top of the file … make all commands and keystrokes
13
+ * work like it does in VS code"). It moved the line from 2026-10-05 to then; the browser has it
14
+ * back. They are one document listener rather than an `onKeyDown` on each
15
15
  * `<textarea>` because desk alone draws a dozen of them by hand: a prop is a thing the next box
16
16
  * forgets, an install at boot is not.
17
17
  *
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * CodeMirror boxes (cb's `CodeEditor`) are `contenteditable`, not `<textarea>`, and are left to
30
30
  * CodeMirror's own keymap: `Mod-Enter` and `Shift-Mod-k` are already in its `defaultKeymap`, and
31
- * `CodeEditor` adds the line above and ⌘↑ / ⌘↓ (⌥↑ / ⌥↓ is `defaultKeymap`'s `moveLineUp`/`Down`).
31
+ * `CodeEditor` adds the line above (⌥↑ / ⌥↓ is `defaultKeymap`'s `moveLineUp`/`Down`).
32
32
  */
33
33
  export type LineCommand = 'below' | 'above' | 'delete' | 'up' | 'down';
34
34
  /**
@@ -50,7 +50,11 @@ export interface LineEdit {
50
50
  export declare function lineEdit(value: string, start: number, end: number, command: 'up' | 'down'): LineEdit | null;
51
51
  export declare function lineEdit(value: string, start: number, end: number, command: 'below' | 'above' | 'delete'): LineEdit;
52
52
  export declare function lineEdit(value: string, start: number, end: number, command: LineCommand): LineEdit | null;
53
- /** Which command a key event asks for, or null. ⌘ on a Mac, Ctrl elsewhere — either is taken. */
53
+ /**
54
+ * Which command a key event asks for, or null. ⌘ on a Mac, Ctrl elsewhere — either is taken —
55
+ * except on an arrow, which moves a line on ⌥ alone: ⌘↑ / ⌘↓ stays the browser's jump to the top
56
+ * or bottom, as in VS Code.
57
+ */
54
58
  export declare function lineCommandOf(event: Pick<KeyboardEvent, 'key' | 'metaKey' | 'ctrlKey' | 'shiftKey' | 'altKey'>): LineCommand | null;
55
59
  /** Writes `edit` into `field` the way typing would: undoable, and seen by React as a change. */
56
60
  export declare function applyLineEdit(field: HTMLTextAreaElement, edit: LineEdit): void;
@@ -1 +1 @@
1
- {"version":3,"file":"lineKeys.d.ts","sourceRoot":"","sources":["../../../src/react/line-keys/lineKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAiBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;AAC7G,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,CAAC;AA+C3G,iGAAiG;AACjG,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC,GAAG,WAAW,GAAG,IAAI,CAQnI;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAgB9E;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,QAAmB,GAAG,MAAM,IAAI,CAQpE"}
1
+ {"version":3,"file":"lineKeys.d.ts","sourceRoot":"","sources":["../../../src/react/line-keys/lineKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAiBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC;AAC7G,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACrH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,CAAC;AA+C3G;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC,GAAG,WAAW,GAAG,IAAI,CAQnI;AAED,gGAAgG;AAChG,wBAAgB,aAAa,CAAC,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI,CAgB9E;AAmBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,QAAmB,GAAG,MAAM,IAAI,CAQpE"}
@@ -4,14 +4,14 @@
4
4
  * ⌘↵ (Ctrl↵) a new line BELOW the caret's line, from anywhere on it
5
5
  * ⇧⌘↵ (Ctrl⇧↵) a new line ABOVE it
6
6
  * ⇧⌘K (Ctrl⇧K) delete the line (every line the selection touches)
7
- * ⌘↑ ⌘↓ (Ctrl↑ Ctrl↓) move the line (every line the selection touches) up or down one
8
- * ⌥↑ ⌥↓ (Alt↑ Alt↓) the same — VS Code's own binding for it
7
+ * ⌥↑ ⌥↓ (Alt↑ Alt↓) move the line (every line the selection touches) up or down one
9
8
  *
10
9
  * The owner asked for these by name (2026-10-05, desk's context components first, "in apps with
11
- * multi line text boxes"; ⌘↑ / ⌘↓ the same day, in his words "like vscode does it"). ⌘↑ / ⌘↓ is
12
- * the owner's chord, and it costs the box the Mac's jump-to-top/bottom (⌘↑ is still that in a
13
- * single-line `<input>`, which this never touches); ⌥↑ / ⌥↓ is what VS Code itself binds, so
14
- * hands trained there find it too. They are one document listener rather than an `onKeyDown` on each
10
+ * multi line text boxes"). ⌘↑ / ⌘↓ is NOT a line key: it is VS Code's — and the Mac's — jump to the
11
+ * top or bottom, the line staying where it is (the owner, 2026-10-14, forge task 3182: "holding
12
+ * command and pushing up should jump to the top of the file … make all commands and keystrokes
13
+ * work like it does in VS code"). It moved the line from 2026-10-05 to then; the browser has it
14
+ * back. They are one document listener rather than an `onKeyDown` on each
15
15
  * `<textarea>` because desk alone draws a dozen of them by hand: a prop is a thing the next box
16
16
  * forgets, an install at boot is not.
17
17
  *
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * CodeMirror boxes (cb's `CodeEditor`) are `contenteditable`, not `<textarea>`, and are left to
30
30
  * CodeMirror's own keymap: `Mod-Enter` and `Shift-Mod-k` are already in its `defaultKeymap`, and
31
- * `CodeEditor` adds the line above and ⌘↑ / ⌘↓ (⌥↑ / ⌥↓ is `defaultKeymap`'s `moveLineUp`/`Down`).
31
+ * `CodeEditor` adds the line above (⌥↑ / ⌥↓ is `defaultKeymap`'s `moveLineUp`/`Down`).
32
32
  */
33
33
  function lineStartOf(value, at) {
34
34
  // `lastIndexOf` clamps a negative start to 0, so at 0 it would find a leading '\n' and answer 1.
@@ -89,12 +89,16 @@ function moveLines(value, start, end, from, to, command) {
89
89
  const shift = next.length + 1;
90
90
  return { from, to: nextEnd, text: `${next}\n${block}`, caret: start + shift, end: end + shift };
91
91
  }
92
- /** Which command a key event asks for, or null. ⌘ on a Mac, Ctrl elsewhere — either is taken. */
92
+ /**
93
+ * Which command a key event asks for, or null. ⌘ on a Mac, Ctrl elsewhere — either is taken —
94
+ * except on an arrow, which moves a line on ⌥ alone: ⌘↑ / ⌘↓ stays the browser's jump to the top
95
+ * or bottom, as in VS Code.
96
+ */
93
97
  export function lineCommandOf(event) {
94
98
  const arrow = event.key === 'ArrowUp' ? 'up' : event.key === 'ArrowDown' ? 'down' : null;
95
99
  // ⇧ with an arrow extends a selection, in every one of these chords; that stays the browser's.
96
100
  if (arrow)
97
- return event.shiftKey || (event.metaKey || event.ctrlKey) === event.altKey ? null : arrow;
101
+ return event.altKey && !event.shiftKey && !event.metaKey && !event.ctrlKey ? arrow : null;
98
102
  if (!(event.metaKey || event.ctrlKey) || event.altKey)
99
103
  return null;
100
104
  if (event.key === 'Enter')
@@ -136,7 +140,7 @@ function onKeyDown(event) {
136
140
  return;
137
141
  event.preventDefault();
138
142
  // A move past the first or last line does nothing — and still swallows the key, as VS Code
139
- // does, rather than letting ⌘↑ jump the caret to the top of the box.
143
+ // does, rather than letting ⌥↑ jump the caret to the start of the paragraph.
140
144
  const edit = lineEdit(field.value, field.selectionStart, field.selectionEnd, command);
141
145
  if (edit)
142
146
  applyLineEdit(field, edit);
@@ -1 +1 @@
1
- {"version":3,"file":"lineKeys.js","sourceRoot":"","sources":["../../../src/react/line-keys/lineKeys.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lineKeys.js","sourceRoot":"","sources":["../../../src/react/line-keys/lineKeys.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "5.79.4",
3
+ "version": "5.80.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
@@ -230,3 +230,49 @@ describe('the rail shows its words on demand (NAV-2, forge task 2748)', () => {
230
230
  expect(triggers).toEqual([screen.getByLabelText('Files') as HTMLButtonElement]);
231
231
  });
232
232
  });
233
+
234
+ describe('the lock (forge task 3183)', () => {
235
+ test('no onLockedChange, no lock', () => {
236
+ render(<ActivityBar top={[item()]} />);
237
+ expect(screen.queryByLabelText('Keep the menu open')).toBeNull();
238
+ });
239
+
240
+ test('pressing it locks the rail open, in the layout; pressing again lets it fold', () => {
241
+ const presses: boolean[] = [];
242
+ const view = render(<ActivityBar top={[item()]} locked={false} onLockedChange={(next) => presses.push(next)} />);
243
+ const rail = screen.getByRole('group');
244
+ const lock = screen.getByLabelText('Keep the menu open');
245
+ expect(lock.getAttribute('aria-pressed')).toBe('false');
246
+ expect(rail.hasAttribute('data-locked')).toBe(false);
247
+ fireEvent.click(lock);
248
+ expect(presses).toEqual([true]);
249
+
250
+ view.rerender(<ActivityBar top={[item()]} locked onLockedChange={(next) => presses.push(next)} />);
251
+ expect(rail.getAttribute('data-locked')).toBe('true');
252
+ expect(rail.getAttribute('data-expanded')).toBe('locked');
253
+ expect(screen.getByLabelText('Keep the menu open').getAttribute('aria-pressed')).toBe('true');
254
+ // Choosing a view does not fold a locked rail.
255
+ fireEvent.click(screen.getByLabelText('Files'));
256
+ expect(rail.getAttribute('data-expanded')).toBe('locked');
257
+ fireEvent.click(screen.getByLabelText('Keep the menu open'));
258
+ expect(presses).toEqual([true, false]);
259
+ });
260
+
261
+ test('a rail that does not expand has no lock, and ignores a saved one', () => {
262
+ render(<ActivityBar top={[item()]} expandable={false} locked onLockedChange={() => {}} />);
263
+ expect(screen.queryByLabelText('Keep the menu open')).toBeNull();
264
+ expect(screen.getByRole('group').hasAttribute('data-locked')).toBe(false);
265
+ });
266
+
267
+ test('on a phone-width screen there is no lock, and a saved one is ignored', () => {
268
+ const real = window.matchMedia;
269
+ window.matchMedia = ((query: string) => ({ ...real.call(window, query), matches: false, addEventListener() {}, removeEventListener() {} })) as typeof window.matchMedia;
270
+ try {
271
+ render(<ActivityBar top={[item()]} locked onLockedChange={() => {}} />);
272
+ expect(screen.queryByLabelText('Keep the menu open')).toBeNull();
273
+ expect(screen.getByRole('group').hasAttribute('data-locked')).toBe(false);
274
+ } finally {
275
+ window.matchMedia = real;
276
+ }
277
+ });
278
+ });
@@ -63,6 +63,14 @@
63
63
  * It folds again when the mouse leaves, focus leaves, a touch lands outside it, Escape is
64
64
  * pressed, or a control is chosen. {@link ActivityBarProps.expandable} `false` opts out.
65
65
  *
66
+ * ── 🔒 It can be LOCKED open (forge task 3183) ──────────────────────────────────────────
67
+ * The owner, 2026-10-14: *"add lock button for the left nav menu. when locked it should stay
68
+ * open and the right collapses to fit screen size. this does not apply to mobile."* Given
69
+ * {@link ActivityBarProps.onLockedChange}, the rail draws a lock at its floor; locked, it stays
70
+ * expanded and takes its expanded width IN the layout — the page beside it narrows instead of
71
+ * being covered, Linear's and Obsidian's pinned sidebar. Under {@link LOCK_MIN_WIDTH} (a phone)
72
+ * there is no lock and a saved one is ignored: a 15rem rail on a 390px screen is the page.
73
+ *
66
74
  * ── Plain CSS, deliberately ─────────────────────────────────────────────────────────────
67
75
  * `activityBar.css` under the `cbab-` prefix, the same convention (and the same reason) as
68
76
  * `fileTree.css` and `denseGallery.css`: this mounts inside apps whose Tailwind build cannot
@@ -83,6 +91,7 @@ import {
83
91
  useState,
84
92
  } from 'react';
85
93
  import { Tooltip } from '../components/Tooltip.js';
94
+ import { useMediaQuery } from '../hooks/useMediaQuery.js';
86
95
  import './activityBar.css';
87
96
  import { cn, RAW_BUTTON } from '../lib/cn.js';
88
97
 
@@ -169,8 +178,20 @@ export interface ActivityBarProps {
169
178
  * icon-only column (a rail whose every control is already named beside it).
170
179
  */
171
180
  expandable?: boolean;
181
+ /**
182
+ * Whether the rail is locked open — see the file header. Controlled: the consumer keeps it
183
+ * (per device, usually) and hears the lock's presses through {@link onLockedChange}.
184
+ */
185
+ locked?: boolean;
186
+ /** Present ⇒ the rail draws its lock. Ignored when {@link expandable} is `false`. */
187
+ onLockedChange?: (locked: boolean) => void;
172
188
  }
173
189
 
190
+ const noop = (): void => {};
191
+
192
+ /** The narrowest screen the lock works on — below it the rail stays an overlay (a phone). */
193
+ export const LOCK_MIN_WIDTH = 768;
194
+
174
195
  /** How long a mouse rests on the rail before it opens its words. */
175
196
  export const EXPAND_HOVER_MS = 350;
176
197
  /** How long a touch holds before the rail opens its words — Android's long-press. */
@@ -180,7 +201,15 @@ const PRESS_SLOP_PX = 10;
180
201
 
181
202
  type ExpandedBy = 'hover' | 'focus' | 'press' | null;
182
203
 
183
- function ActivityButton({ item, onChosen }: { item: ActivityBarItem; onChosen: () => void }): JSX.Element {
204
+ function ActivityButton({
205
+ item,
206
+ onChosen,
207
+ className,
208
+ }: {
209
+ item: ActivityBarItem;
210
+ onChosen: () => void;
211
+ className?: string;
212
+ }): JSX.Element {
184
213
  const disabled = item.unavailableReason !== undefined;
185
214
  const button = (
186
215
  <button
@@ -189,7 +218,7 @@ function ActivityButton({ item, onChosen }: { item: ActivityBarItem; onChosen: (
189
218
  // layer, which fights `.cbab-btn`'s fixed 48px column geometry on cascade order rather
190
219
  // than composing with it — the same split `GalleryActionButton` records.
191
220
  type='button'
192
- className={cn(RAW_BUTTON, 'cbab-btn')}
221
+ className={cn(RAW_BUTTON, 'cbab-btn', className)}
193
222
  data-active={item.active || undefined}
194
223
  onClick={
195
224
  disabled
@@ -233,6 +262,16 @@ function ActivityButton({ item, onChosen }: { item: ActivityBarItem; onChosen: (
233
262
  );
234
263
  }
235
264
 
265
+ /** The lock's glyph — a padlock, its shackle shut while the rail is locked open. */
266
+ function LockGlyph({ locked }: { locked: boolean }): JSX.Element {
267
+ return (
268
+ <svg aria-hidden='true' viewBox='0 0 24 24' fill='none' stroke='currentColor' strokeWidth='1.7' strokeLinecap='round' strokeLinejoin='round'>
269
+ <rect x='5' y='11' width='14' height='10' rx='2' />
270
+ <path d={locked ? 'M8 11V7a4 4 0 0 1 8 0v4' : 'M8 11V7a4 4 0 0 1 7.6-1.7'} />
271
+ </svg>
272
+ );
273
+ }
274
+
236
275
  function Group({ items, onChosen }: { items: readonly ActivityBarItem[]; onChosen: () => void }): JSX.Element | null {
237
276
  if (items.length === 0) return null;
238
277
  return (
@@ -379,8 +418,14 @@ export function ActivityBar({
379
418
  ariaLabel = 'Views and settings',
380
419
  className,
381
420
  expandable = true,
421
+ locked = false,
422
+ onLockedChange,
382
423
  }: ActivityBarProps): JSX.Element {
383
- const { root, by, handlers, collapse } = useExpand(expandable);
424
+ const wide = useMediaQuery(`(min-width: ${LOCK_MIN_WIDTH}px)`);
425
+ const lockable = expandable && onLockedChange !== undefined && wide;
426
+ const isLocked = lockable && locked;
427
+ // Locked, there is nothing to open or fold: the hover, focus and hold machinery stands down.
428
+ const { root, by, handlers, collapse } = useExpand(expandable && !isLocked);
384
429
  return (
385
430
  /*
386
431
  * A `<fieldset>`, matching `DenseGalleryShell`'s `cbgd-order` next to it — a group of
@@ -393,7 +438,8 @@ export function ActivityBar({
393
438
  className={className ? `cbab ${className}` : 'cbab'}
394
439
  aria-label={ariaLabel}
395
440
  data-expandable={expandable || undefined}
396
- data-expanded={by ?? undefined}
441
+ data-expanded={isLocked ? 'locked' : (by ?? undefined)}
442
+ data-locked={isLocked || undefined}
397
443
  {...handlers}
398
444
  >
399
445
  {/* The panel is what widens; the fieldset keeps its 48px track, so expanding is an
@@ -405,6 +451,23 @@ export function ActivityBar({
405
451
  starts its top group at the top instead of centring it. */}
406
452
  <div className='cbab-spacer' />
407
453
  <Group items={bottom} onChosen={collapse} />
454
+ {lockable && onLockedChange ? (
455
+ <div className='cbab-group'>
456
+ <ActivityButton
457
+ className='cbab-lock'
458
+ // A stable name with the state in aria-pressed — ActivityBarItem.label's own rule.
459
+ item={{
460
+ id: 'cbab-lock',
461
+ label: 'Keep the menu open',
462
+ title: isLocked ? 'Let the menu fold away again' : 'Keep the menu open — the page beside it makes room',
463
+ icon: <LockGlyph locked={isLocked} />,
464
+ active: isLocked,
465
+ onSelect: () => onLockedChange(!isLocked),
466
+ }}
467
+ onChosen={noop}
468
+ />
469
+ </div>
470
+ ) : null}
408
471
  </div>
409
472
  </fieldset>
410
473
  );
@@ -56,6 +56,26 @@
56
56
  box-shadow: var(--shadow-overlay);
57
57
  }
58
58
 
59
+ /*
60
+ * 🔒 Locked open (forge task 3183): the TRACK takes the expanded width, so the page beside the
61
+ * rail narrows to fit instead of being covered — no overlay shadow, no lift over its neighbours.
62
+ * The component only sets `data-locked` on a screen at least LOCK_MIN_WIDTH wide.
63
+ */
64
+ .cbab[data-locked] {
65
+ inline-size: var(--cbab-expanded);
66
+ flex-basis: var(--cbab-expanded);
67
+ z-index: auto;
68
+ }
69
+
70
+ .cbab[data-locked] .cbab-panel {
71
+ box-shadow: none;
72
+ }
73
+
74
+ /* The lock is a toggle, not a place: pressed, it is lit, without the "you are here" hairline. */
75
+ .cbab .cbab-lock[data-active]::before {
76
+ content: none;
77
+ }
78
+
59
79
  @media (prefers-reduced-motion: reduce) {
60
80
  .cbab-panel {
61
81
  transition: none;
@@ -9,7 +9,7 @@
9
9
  // RHF via Controller:
10
10
  // <Controller control={control} name='query'
11
11
  // render={({ field }) => <CodeEditor value={field.value} onChange={field.onChange} language='sql' />} />
12
- import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
12
+ import { history } from '@codemirror/commands';
13
13
  import {
14
14
  bracketMatching,
15
15
  defaultHighlightStyle,
@@ -20,12 +20,19 @@ import {
20
20
  } from '@codemirror/language';
21
21
  import type { Extension } from '@codemirror/state';
22
22
  import { Compartment, EditorState, Transaction } from '@codemirror/state';
23
- import { EditorView, keymap, lineNumbers as lineNumbersExt, placeholder as placeholderExt } from '@codemirror/view';
23
+ import {
24
+ drawSelection,
25
+ EditorView,
26
+ keymap,
27
+ lineNumbers as lineNumbersExt,
28
+ placeholder as placeholderExt,
29
+ } from '@codemirror/view';
24
30
  import { useEffect, useMemo, useRef, useState } from 'react';
25
31
  import { cn } from '../lib/cn.js';
26
32
  import type { CodeLanguage } from './languages.js';
27
33
  import { loadLanguageExtension } from './languages.js';
28
- import { lineKeyBindings } from './lineAbove.js';
34
+ import { editorKeyBindings } from './keys.js';
35
+ import { plainInput } from './plainInput.js';
29
36
  import { cbEditorTheme, cbSyntaxHighlighting } from './theme.js';
30
37
 
31
38
  export interface CodeEditorProps {
@@ -45,7 +52,11 @@ export interface CodeEditorProps {
45
52
  lineWrapping?: boolean;
46
53
  /** Text shown, dimmed, while the document is empty. */
47
54
  placeholder?: string;
48
- /** Let the browser spell-check the text — prose like markdown wants it, source does not. Default: false. */
55
+ /**
56
+ * Let the browser spell-check the text — prose like markdown wants it, source does not. Default:
57
+ * false. Only the red underline: autocorrect stays CodeMirror's `off` either way, because an OS
58
+ * that rewrites `--` to `—` or `"` to `“` rewrites the syntax (forge task 3184, `plainInput`).
59
+ */
49
60
  spellCheck?: boolean;
50
61
  /**
51
62
  * Called once with the live `EditorView` after it mounts — the seam for imperative work a
@@ -67,9 +78,15 @@ export interface CodeEditorProps {
67
78
  'aria-label'?: string;
68
79
  }
69
80
 
70
- // Fixed extensions shared across all instances — created once.
81
+ // Fixed extensions shared across all instances — created once. Several cursors, as in VS Code:
82
+ // ⌥-click adds one, ⌥⌘↑ / ⌥⌘↓ (`defaultKeymap`) one above or below, ⌘D the next match, Esc
83
+ // back to one — `drawSelection` is what paints the cursors past the browser's single caret.
71
84
  const FIXED_EXTENSIONS: Extension[] = [
72
85
  history(),
86
+ EditorState.allowMultipleSelections.of(true),
87
+ drawSelection(),
88
+ EditorView.clickAddsSelectionRange.of((event) => event.altKey),
89
+ plainInput,
73
90
  indentOnInput(),
74
91
  bracketMatching(),
75
92
  syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
@@ -125,7 +142,7 @@ export function CodeEditor({
125
142
  const dynExtensions = useMemo<Extension[]>(() => {
126
143
  const exts: Extension[] = [
127
144
  indentUnit.of(' '.repeat(tabSize)),
128
- keymap.of([...lineKeyBindings, ...defaultKeymap, ...historyKeymap, indentWithTab]),
145
+ keymap.of(editorKeyBindings),
129
146
  ];
130
147
  if (showLineNumbers) exts.push(lineNumbersExt());
131
148
  if (showFoldGutter) exts.push(foldGutterExt());
@@ -133,7 +150,7 @@ export function CodeEditor({
133
150
  if (placeholder) exts.push(placeholderExt(placeholder));
134
151
  // The name belongs on the element that takes focus — the textbox — not only on the wrapper.
135
152
  if (ariaLabel) exts.push(EditorView.contentAttributes.of({ 'aria-label': ariaLabel }));
136
- if (spellCheck) exts.push(EditorView.contentAttributes.of({ spellcheck: 'true', autocorrect: 'on', autocapitalize: 'on' }));
153
+ if (spellCheck) exts.push(EditorView.contentAttributes.of({ spellcheck: 'true' }));
137
154
  if (readOnly) exts.push(EditorState.readOnly.of(true));
138
155
  if (langExtension) exts.push(langExtension);
139
156
  exts.push(...extraExtensions);