@pierre/diffs 1.3.0 → 1.3.2

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 (76) hide show
  1. package/dist/components/CodeView.d.ts +4 -1
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +60 -26
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +1 -0
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +18 -9
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +4 -0
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +48 -21
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/VirtualizedFile.d.ts +1 -0
  14. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  15. package/dist/components/VirtualizedFile.js +3 -0
  16. package/dist/components/VirtualizedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFileDiff.d.ts +1 -0
  18. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFileDiff.js +3 -0
  20. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  21. package/dist/edit/index.d.ts +4 -3
  22. package/dist/editor/command.d.ts +10 -2
  23. package/dist/editor/command.d.ts.map +1 -1
  24. package/dist/editor/command.js +113 -39
  25. package/dist/editor/command.js.map +1 -1
  26. package/dist/editor/editStack.d.ts +2 -2
  27. package/dist/editor/editStack.d.ts.map +1 -1
  28. package/dist/editor/editStack.js.map +1 -1
  29. package/dist/editor/editor.d.ts +5 -2
  30. package/dist/editor/editor.d.ts.map +1 -1
  31. package/dist/editor/editor.js +49 -29
  32. package/dist/editor/editor.js.map +1 -1
  33. package/dist/editor/pieceTable.d.ts +1 -2
  34. package/dist/editor/pieceTable.d.ts.map +1 -1
  35. package/dist/editor/pieceTable.js.map +1 -1
  36. package/dist/editor/platform.d.ts +3 -11
  37. package/dist/editor/platform.d.ts.map +1 -1
  38. package/dist/editor/platform.js +2 -28
  39. package/dist/editor/platform.js.map +1 -1
  40. package/dist/editor/searchPanel.d.ts +2 -1
  41. package/dist/editor/searchPanel.d.ts.map +1 -1
  42. package/dist/editor/searchPanel.js.map +1 -1
  43. package/dist/editor/selection.d.ts +2 -2
  44. package/dist/editor/selection.d.ts.map +1 -1
  45. package/dist/editor/selection.js.map +1 -1
  46. package/dist/editor/selectionAction.d.ts +2 -1
  47. package/dist/editor/selectionAction.d.ts.map +1 -1
  48. package/dist/editor/selectionAction.js +3 -1
  49. package/dist/editor/selectionAction.js.map +1 -1
  50. package/dist/editor/textDocument.d.ts +4 -14
  51. package/dist/editor/textDocument.d.ts.map +1 -1
  52. package/dist/editor/textDocument.js +7 -0
  53. package/dist/editor/textDocument.js.map +1 -1
  54. package/dist/index.d.ts +2 -2
  55. package/dist/react/CodeView.d.ts +1 -0
  56. package/dist/react/CodeView.d.ts.map +1 -1
  57. package/dist/react/CodeView.js +9 -0
  58. package/dist/react/CodeView.js.map +1 -1
  59. package/dist/react/index.d.ts +2 -2
  60. package/dist/ssr/index.d.ts +2 -2
  61. package/dist/types.d.ts +22 -2
  62. package/dist/types.d.ts.map +1 -1
  63. package/dist/utils/areManagedSnapshotsEqual.js +1 -1
  64. package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
  65. package/dist/utils/guardWebKitScrollDuringRebuild.d.ts +20 -0
  66. package/dist/utils/guardWebKitScrollDuringRebuild.d.ts.map +1 -0
  67. package/dist/utils/guardWebKitScrollDuringRebuild.js +34 -0
  68. package/dist/utils/guardWebKitScrollDuringRebuild.js.map +1 -0
  69. package/dist/utils/platform.d.ts +14 -0
  70. package/dist/utils/platform.d.ts.map +1 -0
  71. package/dist/utils/platform.js +32 -0
  72. package/dist/utils/platform.js.map +1 -0
  73. package/dist/worker/wasm-B9ZqxnKj.js.map +1 -1
  74. package/dist/worker/worker-portable.js +13 -12
  75. package/dist/worker/worker-portable.js.map +1 -1
  76. package/package.json +1 -1
@@ -1,47 +1,121 @@
1
- import { isMacLike, isPrimaryModifier } from "./platform.js";
1
+ import { isMacLike } from "../utils/platform.js";
2
+ import { isPrimaryModifier } from "./platform.js";
2
3
  //#region src/editor/command.ts
3
- const SHORTCUTS = {
4
- a: "selectAll",
5
- d: "findNextMatch"
6
- };
7
- function resolveEditorCommandFromKeyboardEvent(event, isMac = isMacLike()) {
8
- const hasPrimaryModifier = isPrimaryModifier(event, isMac);
9
- const { shiftKey, altKey, key } = event;
10
- const normalizedKey = key.length === 1 ? key.toLowerCase() : key;
11
- if (isMac && event.ctrlKey && !event.metaKey && !shiftKey && !altKey && (normalizedKey === "k" || event.code === "KeyK")) return "deleteHardLineForward";
12
- if (!event.metaKey && !event.ctrlKey && shiftKey && altKey && (normalizedKey === "a" || event.code === "KeyA")) return "toggleBlockComment";
13
- if (!event.metaKey && !event.ctrlKey && shiftKey && altKey) {
14
- if (normalizedKey === "ArrowUp") return "copyLineUp";
15
- if (normalizedKey === "ArrowDown") return "copyLineDown";
16
- }
17
- if (!event.metaKey && !event.ctrlKey && !shiftKey && !altKey && normalizedKey === "Escape") return "simplifySelection";
18
- if (!event.metaKey && !shiftKey) {
19
- if (!event.ctrlKey && altKey && normalizedKey === "ArrowUp") return "moveLineUp";
20
- if (!event.ctrlKey && altKey && normalizedKey === "ArrowDown") return "moveLineDown";
21
- if ((isMac || navigator.platform.includes("Linux")) && event.ctrlKey && altKey) {
22
- if (normalizedKey === "p" || event.code === "KeyP") return "moveLineUp";
23
- if (normalizedKey === "n" || event.code === "KeyN") return "moveLineDown";
4
+ const compiledEditorKeymaps = /* @__PURE__ */ new WeakMap();
5
+ const compiledDefaultKeymap = getCompiledEditorKeymap([
6
+ { bindings: {
7
+ Tab: "indent",
8
+ "shift+Tab": "outdent",
9
+ "cmdOrCtrl+[": "indentLess",
10
+ "cmdOrCtrl+]": "indentMore",
11
+ "cmdOrCtrl+z": "undo",
12
+ "cmdOrCtrl+shift+z": "redo",
13
+ "cmdOrCtrl+a": "selectAll",
14
+ "cmdOrCtrl+d": "findNextMatch",
15
+ "cmdOrCtrl+f": "openSearchPanel",
16
+ "cmdOrCtrl+alt+f": "openSearchReplacePanel",
17
+ "alt+ArrowUp": "moveLineUp",
18
+ "alt+ArrowDown": "moveLineDown",
19
+ "shift+alt+ArrowUp": "copyLineUp",
20
+ "shift+alt+ArrowDown": "copyLineDown",
21
+ Escape: "simplifySelection",
22
+ "cmdOrCtrl+Enter": "insertBlankLine",
23
+ "cmdOrCtrl+/": "toggleComment",
24
+ "shift+alt+a": "toggleBlockComment",
25
+ "cmdOrCtrl+Home": "moveCursorToDocStart",
26
+ "cmdOrCtrl+End": "moveCursorToDocEnd",
27
+ "cmdOrCtrl+shift+Home": "expandSelectionDocStart",
28
+ "cmdOrCtrl+shift+End": "expandSelectionDocEnd"
29
+ } },
30
+ {
31
+ platform: "mac",
32
+ bindings: {
33
+ "ctrl+k": "deleteHardLineForward",
34
+ "ctrl+alt+p": "moveLineUp",
35
+ "ctrl+alt+n": "moveLineDown",
36
+ "cmd+ArrowUp": "moveCursorToDocStart",
37
+ "cmd+ArrowDown": "moveCursorToDocEnd",
38
+ "cmd+shift+ArrowUp": "expandSelectionDocStart",
39
+ "cmd+shift+ArrowDown": "expandSelectionDocEnd"
40
+ }
41
+ },
42
+ {
43
+ platform: "windows",
44
+ bindings: { "ctrl+y": "redo" }
45
+ },
46
+ {
47
+ platform: "linux",
48
+ bindings: {
49
+ "ctrl+y": "redo",
50
+ "ctrl+alt+p": "moveLineUp",
51
+ "ctrl+alt+n": "moveLineDown"
24
52
  }
25
53
  }
26
- if (hasPrimaryModifier && (normalizedKey === "f" || event.code === "KeyF")) return altKey ? "openSearchReplacePanel" : "openSearchPanel";
27
- if (altKey) return;
28
- if (!hasPrimaryModifier && normalizedKey === "Tab") return shiftKey ? "outdent" : "indent";
29
- if (!hasPrimaryModifier) return;
30
- if (!shiftKey && normalizedKey === "Enter") return "insertBlankLine";
31
- if (!shiftKey && (normalizedKey === "/" || event.code === "Slash")) return "toggleComment";
32
- if (!shiftKey && normalizedKey === "[") return "indentLess";
33
- if (!shiftKey && normalizedKey === "]") return "indentMore";
34
- if (normalizedKey === "z") return shiftKey ? "redo" : "undo";
35
- if (!isMac && normalizedKey === "y") return "redo";
36
- if (normalizedKey === "Home" || isMac && normalizedKey === "ArrowUp") {
37
- if (shiftKey) return "expandSelectionDocStart";
38
- return "moveCursorToDocStart";
54
+ ]);
55
+ const keyboardCodeKeys = {
56
+ Backquote: "`",
57
+ Minus: "-",
58
+ Equal: "=",
59
+ Comma: ",",
60
+ Period: ".",
61
+ Slash: "/",
62
+ Semicolon: ";",
63
+ Quote: "'",
64
+ BracketLeft: "[",
65
+ BracketRight: "]",
66
+ Backslash: "\\",
67
+ Space: "Space"
68
+ };
69
+ function getCompiledEditorKeymap(keymap) {
70
+ let compiledKeymap = compiledEditorKeymaps.get(keymap);
71
+ if (compiledKeymap === void 0) {
72
+ compiledKeymap = {
73
+ mac: /* @__PURE__ */ new Map(),
74
+ windows: /* @__PURE__ */ new Map(),
75
+ linux: /* @__PURE__ */ new Map()
76
+ };
77
+ for (const entry of keymap) for (const shortcut of Object.keys(entry.bindings)) {
78
+ const command = entry.bindings[shortcut];
79
+ if (command === void 0) continue;
80
+ const parts = shortcut.split("+");
81
+ const shortcutKey = parts.pop();
82
+ for (const platform of [
83
+ "mac",
84
+ "windows",
85
+ "linux"
86
+ ]) {
87
+ if (entry.platform !== void 0 && entry.platform !== platform) continue;
88
+ let modifiers = 0;
89
+ for (const modifier of parts) if (modifier === "alt") modifiers |= 1;
90
+ else if (modifier === "ctrl") modifiers |= 2;
91
+ else if (modifier === "cmd") modifiers |= 4;
92
+ else if (modifier === "shift") modifiers |= 8;
93
+ else if (modifier === "cmdOrCtrl") modifiers |= platform === "mac" ? 4 : 2;
94
+ let bindings = compiledKeymap[platform].get(modifiers);
95
+ if (bindings === void 0) {
96
+ bindings = /* @__PURE__ */ new Map();
97
+ compiledKeymap[platform].set(modifiers, bindings);
98
+ }
99
+ bindings.set(shortcutKey, command);
100
+ }
101
+ }
102
+ compiledEditorKeymaps.set(keymap, compiledKeymap);
39
103
  }
40
- if (normalizedKey === "End" || isMac && normalizedKey === "ArrowDown") {
41
- if (shiftKey) return "expandSelectionDocEnd";
42
- return "moveCursorToDocEnd";
104
+ return compiledKeymap;
105
+ }
106
+ function resolveEditorCommandFromKeyboardEvent(event, keymap, isMac = isMacLike()) {
107
+ const eventKey = event.key === " " ? "Space" : event.key.length === 1 ? event.key.toLowerCase() : event.key;
108
+ const code = event.code ?? "";
109
+ const codeKey = code.startsWith("Key") && code.length === 4 ? code.slice(3).toLowerCase() : code.startsWith("Digit") && code.length === 6 ? code.slice(5) : keyboardCodeKeys[code];
110
+ const platform = isMac ? "mac" : globalThis.navigator?.platform.includes("Linux") === true ? "linux" : "windows";
111
+ const modifiers = (event.altKey ? 1 : 0) | (event.ctrlKey ? 2 : 0) | (event.metaKey ? 4 : 0) | (event.shiftKey ? 8 : 0);
112
+ if (keymap !== void 0) {
113
+ const bindings = getCompiledEditorKeymap(keymap)[platform].get(modifiers);
114
+ const command = bindings?.get(eventKey) ?? (codeKey === void 0 ? void 0 : bindings?.get(codeKey));
115
+ if (command !== void 0) return command;
43
116
  }
44
- return SHORTCUTS[normalizedKey];
117
+ const bindings = compiledDefaultKeymap[platform].get(modifiers);
118
+ return bindings?.get(eventKey) ?? (codeKey === void 0 ? void 0 : bindings?.get(codeKey));
45
119
  }
46
120
  function resolveFindAgainShortcut(event, isMac = isMacLike()) {
47
121
  if (event.altKey) return;
@@ -1 +1 @@
1
- {"version":3,"file":"command.js","names":[],"sources":["../../src/editor/command.ts"],"sourcesContent":["import { isMacLike, isPrimaryModifier } from './platform';\n\nexport type EditorCommand =\n | 'indent'\n | 'outdent'\n | 'indentLess'\n | 'indentMore'\n | 'undo'\n | 'redo'\n | 'selectAll'\n | 'findNextMatch'\n | 'openSearchPanel'\n | 'openSearchReplacePanel'\n | 'moveLineUp'\n | 'moveLineDown'\n | 'copyLineUp'\n | 'copyLineDown'\n | 'simplifySelection'\n | 'insertBlankLine'\n | 'deleteHardLineForward'\n | 'toggleComment'\n | 'toggleBlockComment'\n | 'moveCursorToDocStart'\n | 'moveCursorToDocEnd'\n | 'expandSelectionDocStart'\n | 'expandSelectionDocEnd';\n\nconst SHORTCUTS: Partial<Record<string, EditorCommand>> = {\n a: 'selectAll',\n d: 'findNextMatch',\n};\n\nexport function resolveEditorCommandFromKeyboardEvent(\n event: KeyboardEvent,\n isMac: boolean = isMacLike()\n): EditorCommand | undefined {\n const hasPrimaryModifier = isPrimaryModifier(event, isMac);\n const { shiftKey, altKey, key } = event;\n\n const normalizedKey = key.length === 1 ? key.toLowerCase() : key;\n\n // Safari and Firefox do not report macOS control+k as a beforeinput action,\n // so resolve the native delete-to-line-end shortcut from keydown instead.\n if (\n isMac &&\n event.ctrlKey &&\n !event.metaKey &&\n !shiftKey &&\n !altKey &&\n (normalizedKey === 'k' || event.code === 'KeyK')\n ) {\n return 'deleteHardLineForward';\n }\n\n if (\n !event.metaKey &&\n !event.ctrlKey &&\n shiftKey &&\n altKey &&\n (normalizedKey === 'a' || event.code === 'KeyA')\n ) {\n return 'toggleBlockComment';\n }\n\n if (!event.metaKey && !event.ctrlKey && shiftKey && altKey) {\n if (normalizedKey === 'ArrowUp') {\n return 'copyLineUp';\n }\n if (normalizedKey === 'ArrowDown') {\n return 'copyLineDown';\n }\n }\n\n if (\n !event.metaKey &&\n !event.ctrlKey &&\n !shiftKey &&\n !altKey &&\n normalizedKey === 'Escape'\n ) {\n return 'simplifySelection';\n }\n\n if (!event.metaKey && !shiftKey) {\n if (!event.ctrlKey && altKey && normalizedKey === 'ArrowUp') {\n return 'moveLineUp';\n }\n if (!event.ctrlKey && altKey && normalizedKey === 'ArrowDown') {\n return 'moveLineDown';\n }\n if (\n (isMac || navigator.platform.includes('Linux')) &&\n event.ctrlKey &&\n altKey\n ) {\n if (normalizedKey === 'p' || event.code === 'KeyP') {\n return 'moveLineUp';\n }\n if (normalizedKey === 'n' || event.code === 'KeyN') {\n return 'moveLineDown';\n }\n }\n }\n\n // cmd/ctrl+f opens the search panel in find mode; adding alt opens it in\n // find/replace mode. macOS emits a dead key for Option+F (key === 'ƒ'), so\n // fall back to event.code to detect the physical F key.\n if (hasPrimaryModifier && (normalizedKey === 'f' || event.code === 'KeyF')) {\n return altKey ? 'openSearchReplacePanel' : 'openSearchPanel';\n }\n\n if (altKey) {\n return undefined;\n }\n\n if (!hasPrimaryModifier && normalizedKey === 'Tab') {\n return shiftKey ? 'outdent' : 'indent';\n }\n\n if (!hasPrimaryModifier) {\n return undefined;\n }\n\n if (!shiftKey && normalizedKey === 'Enter') {\n return 'insertBlankLine';\n }\n\n if (!shiftKey && (normalizedKey === '/' || event.code === 'Slash')) {\n return 'toggleComment';\n }\n\n if (!shiftKey && normalizedKey === '[') {\n return 'indentLess';\n }\n\n if (!shiftKey && normalizedKey === ']') {\n return 'indentMore';\n }\n\n if (normalizedKey === 'z') {\n return shiftKey ? 'redo' : 'undo';\n }\n\n if (!isMac && normalizedKey === 'y') {\n return 'redo';\n }\n\n if (normalizedKey === 'Home' || (isMac && normalizedKey === 'ArrowUp')) {\n if (shiftKey) {\n return 'expandSelectionDocStart';\n }\n return 'moveCursorToDocStart';\n }\n\n if (normalizedKey === 'End' || (isMac && normalizedKey === 'ArrowDown')) {\n if (shiftKey) {\n return 'expandSelectionDocEnd';\n }\n return 'moveCursorToDocEnd';\n }\n\n return SHORTCUTS[normalizedKey];\n}\n\n// Detects the native \"find again\" shortcut (cmd/ctrl+g for next, adding shift\n// for previous) so the find/replace panel can override the browser default and\n// step through matches. macOS can emit a dead key for the physical G, so fall\n// back to event.code like the cmd+f handling above.\nexport function resolveFindAgainShortcut(\n event: KeyboardEvent,\n isMac: boolean = isMacLike()\n): 'next' | 'previous' | undefined {\n if (event.altKey) {\n return undefined;\n }\n if (!isPrimaryModifier(event, isMac)) {\n return undefined;\n }\n const normalizedKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n if (normalizedKey === 'g' || event.code === 'KeyG') {\n return event.shiftKey ? 'previous' : 'next';\n }\n return undefined;\n}\n"],"mappings":";;AA2BA,MAAM,YAAoD;CACxD,GAAG;CACH,GAAG;AACL;AAEA,SAAgB,sCACd,OACA,QAAiB,UAAU,GACA;CAC3B,MAAM,qBAAqB,kBAAkB,OAAO,KAAK;CACzD,MAAM,EAAE,UAAU,QAAQ,QAAQ;CAElC,MAAM,gBAAgB,IAAI,WAAW,IAAI,IAAI,YAAY,IAAI;CAI7D,IACE,SACA,MAAM,WACN,CAAC,MAAM,WACP,CAAC,YACD,CAAC,WACA,kBAAkB,OAAO,MAAM,SAAS,SAEzC,OAAO;CAGT,IACE,CAAC,MAAM,WACP,CAAC,MAAM,WACP,YACA,WACC,kBAAkB,OAAO,MAAM,SAAS,SAEzC,OAAO;CAGT,IAAI,CAAC,MAAM,WAAW,CAAC,MAAM,WAAW,YAAY,QAAQ;EAC1D,IAAI,kBAAkB,WACpB,OAAO;EAET,IAAI,kBAAkB,aACpB,OAAO;CAEX;CAEA,IACE,CAAC,MAAM,WACP,CAAC,MAAM,WACP,CAAC,YACD,CAAC,UACD,kBAAkB,UAElB,OAAO;CAGT,IAAI,CAAC,MAAM,WAAW,CAAC,UAAU;EAC/B,IAAI,CAAC,MAAM,WAAW,UAAU,kBAAkB,WAChD,OAAO;EAET,IAAI,CAAC,MAAM,WAAW,UAAU,kBAAkB,aAChD,OAAO;EAET,KACG,SAAS,UAAU,SAAS,SAAS,OAAO,MAC7C,MAAM,WACN,QACA;GACA,IAAI,kBAAkB,OAAO,MAAM,SAAS,QAC1C,OAAO;GAET,IAAI,kBAAkB,OAAO,MAAM,SAAS,QAC1C,OAAO;EAEX;CACF;CAKA,IAAI,uBAAuB,kBAAkB,OAAO,MAAM,SAAS,SACjE,OAAO,SAAS,2BAA2B;CAG7C,IAAI,QACF;CAGF,IAAI,CAAC,sBAAsB,kBAAkB,OAC3C,OAAO,WAAW,YAAY;CAGhC,IAAI,CAAC,oBACH;CAGF,IAAI,CAAC,YAAY,kBAAkB,SACjC,OAAO;CAGT,IAAI,CAAC,aAAa,kBAAkB,OAAO,MAAM,SAAS,UACxD,OAAO;CAGT,IAAI,CAAC,YAAY,kBAAkB,KACjC,OAAO;CAGT,IAAI,CAAC,YAAY,kBAAkB,KACjC,OAAO;CAGT,IAAI,kBAAkB,KACpB,OAAO,WAAW,SAAS;CAG7B,IAAI,CAAC,SAAS,kBAAkB,KAC9B,OAAO;CAGT,IAAI,kBAAkB,UAAW,SAAS,kBAAkB,WAAY;EACtE,IAAI,UACF,OAAO;EAET,OAAO;CACT;CAEA,IAAI,kBAAkB,SAAU,SAAS,kBAAkB,aAAc;EACvE,IAAI,UACF,OAAO;EAET,OAAO;CACT;CAEA,OAAO,UAAU;AACnB;AAMA,SAAgB,yBACd,OACA,QAAiB,UAAU,GACM;CACjC,IAAI,MAAM,QACR;CAEF,IAAI,CAAC,kBAAkB,OAAO,KAAK,GACjC;CAIF,KADE,MAAM,IAAI,WAAW,IAAI,MAAM,IAAI,YAAY,IAAI,MAAM,SACrC,OAAO,MAAM,SAAS,QAC1C,OAAO,MAAM,WAAW,aAAa;AAGzC"}
1
+ {"version":3,"file":"command.js","names":[],"sources":["../../src/editor/command.ts"],"sourcesContent":["import { isMacLike, isPrimaryModifier } from './platform';\n\nexport type EditorCommand =\n | 'indent'\n | 'outdent'\n | 'indentLess'\n | 'indentMore'\n | 'undo'\n | 'redo'\n | 'selectAll'\n | 'findNextMatch'\n | 'openSearchPanel'\n | 'openSearchReplacePanel'\n | 'moveLineUp'\n | 'moveLineDown'\n | 'copyLineUp'\n | 'copyLineDown'\n | 'simplifySelection'\n | 'insertBlankLine'\n | 'deleteHardLineForward'\n | 'toggleComment'\n | 'toggleBlockComment'\n | 'moveCursorToDocStart'\n | 'moveCursorToDocEnd'\n | 'expandSelectionDocStart'\n | 'expandSelectionDocEnd';\n\nexport type KeyboardKey =\n | 'a'\n | 'b'\n | 'c'\n | 'd'\n | 'e'\n | 'f'\n | 'g'\n | 'h'\n | 'i'\n | 'j'\n | 'k'\n | 'l'\n | 'm'\n | 'n'\n | 'o'\n | 'p'\n | 'q'\n | 'r'\n | 's'\n | 't'\n | 'u'\n | 'v'\n | 'w'\n | 'x'\n | 'y'\n | 'z'\n | '0'\n | '1'\n | '2'\n | '3'\n | '4'\n | '5'\n | '6'\n | '7'\n | '8'\n | '9'\n | '`'\n | '-'\n | '='\n | ','\n | '.'\n | '/'\n | ';'\n | \"'\"\n | '['\n | ']'\n | '\\\\'\n | 'Space'\n | 'Tab'\n | 'Enter'\n | 'Escape'\n | 'Backspace'\n | 'Delete'\n | 'ArrowUp'\n | 'ArrowDown'\n | 'ArrowLeft'\n | 'ArrowRight'\n | 'Home'\n | 'End'\n | 'PageUp'\n | 'PageDown'\n | 'F1'\n | 'F2'\n | 'F3'\n | 'F4'\n | 'F5'\n | 'F6'\n | 'F7'\n | 'F8'\n | 'F9'\n | 'F10'\n | 'F11'\n | 'F12';\n\nexport type KeyboardModifier = 'cmdOrCtrl' | 'shift' | 'alt' | 'ctrl' | 'cmd';\n\nexport type EditorShortcut =\n | KeyboardKey\n | `${KeyboardModifier}+${KeyboardKey}`\n | `${KeyboardModifier}+${KeyboardModifier}+${KeyboardKey}`;\n\n/** Later groups take precedence when bindings overlap. */\nexport type EditorKeymap = ReadonlyArray<{\n /** Undefined applies on every platform. */\n readonly platform?: 'mac' | 'windows' | 'linux';\n readonly bindings: Readonly<Partial<Record<EditorShortcut, EditorCommand>>>;\n}>;\n\ntype CompiledEditorKeymap = Record<\n 'mac' | 'windows' | 'linux',\n Map<number, Map<KeyboardKey, EditorCommand>>\n>;\n\nconst compiledEditorKeymaps = new WeakMap<EditorKeymap, CompiledEditorKeymap>();\n\nconst defaultKeymap = [\n {\n bindings: {\n Tab: 'indent',\n 'shift+Tab': 'outdent',\n 'cmdOrCtrl+[': 'indentLess',\n 'cmdOrCtrl+]': 'indentMore',\n 'cmdOrCtrl+z': 'undo',\n 'cmdOrCtrl+shift+z': 'redo',\n 'cmdOrCtrl+a': 'selectAll',\n 'cmdOrCtrl+d': 'findNextMatch',\n 'cmdOrCtrl+f': 'openSearchPanel',\n 'cmdOrCtrl+alt+f': 'openSearchReplacePanel',\n 'alt+ArrowUp': 'moveLineUp',\n 'alt+ArrowDown': 'moveLineDown',\n 'shift+alt+ArrowUp': 'copyLineUp',\n 'shift+alt+ArrowDown': 'copyLineDown',\n Escape: 'simplifySelection',\n 'cmdOrCtrl+Enter': 'insertBlankLine',\n 'cmdOrCtrl+/': 'toggleComment',\n 'shift+alt+a': 'toggleBlockComment',\n 'cmdOrCtrl+Home': 'moveCursorToDocStart',\n 'cmdOrCtrl+End': 'moveCursorToDocEnd',\n 'cmdOrCtrl+shift+Home': 'expandSelectionDocStart',\n 'cmdOrCtrl+shift+End': 'expandSelectionDocEnd',\n },\n },\n {\n platform: 'mac',\n bindings: {\n 'ctrl+k': 'deleteHardLineForward',\n 'ctrl+alt+p': 'moveLineUp',\n 'ctrl+alt+n': 'moveLineDown',\n 'cmd+ArrowUp': 'moveCursorToDocStart',\n 'cmd+ArrowDown': 'moveCursorToDocEnd',\n 'cmd+shift+ArrowUp': 'expandSelectionDocStart',\n 'cmd+shift+ArrowDown': 'expandSelectionDocEnd',\n },\n },\n {\n platform: 'windows',\n bindings: {\n 'ctrl+y': 'redo',\n },\n },\n {\n platform: 'linux',\n bindings: {\n 'ctrl+y': 'redo',\n 'ctrl+alt+p': 'moveLineUp',\n 'ctrl+alt+n': 'moveLineDown',\n },\n },\n] satisfies EditorKeymap;\n\nconst compiledDefaultKeymap = getCompiledEditorKeymap(defaultKeymap);\n\nconst keyboardCodeKeys: Partial<Record<string, KeyboardKey>> = {\n Backquote: '`',\n Minus: '-',\n Equal: '=',\n Comma: ',',\n Period: '.',\n Slash: '/',\n Semicolon: ';',\n Quote: \"'\",\n BracketLeft: '[',\n BracketRight: ']',\n Backslash: '\\\\',\n Space: 'Space',\n};\n\nfunction getCompiledEditorKeymap(keymap: EditorKeymap): CompiledEditorKeymap {\n let compiledKeymap = compiledEditorKeymaps.get(keymap);\n if (compiledKeymap === undefined) {\n compiledKeymap = {\n mac: new Map(),\n windows: new Map(),\n linux: new Map(),\n };\n\n // Compile once so keydown resolves by modifier mask and key without\n // scanning or splitting every configured shortcut.\n for (const entry of keymap) {\n for (const shortcut of Object.keys(entry.bindings) as EditorShortcut[]) {\n const command = entry.bindings[shortcut];\n if (command === undefined) {\n continue;\n }\n const parts = shortcut.split('+');\n const shortcutKey = parts.pop() as KeyboardKey;\n\n for (const platform of ['mac', 'windows', 'linux'] as const) {\n if (entry.platform !== undefined && entry.platform !== platform) {\n continue;\n }\n let modifiers = 0;\n for (const modifier of parts as KeyboardModifier[]) {\n if (modifier === 'alt') {\n modifiers |= 1;\n } else if (modifier === 'ctrl') {\n modifiers |= 2;\n } else if (modifier === 'cmd') {\n modifiers |= 4;\n } else if (modifier === 'shift') {\n modifiers |= 8;\n } else if (modifier === 'cmdOrCtrl') {\n modifiers |= platform === 'mac' ? 4 : 2;\n }\n }\n let bindings = compiledKeymap[platform].get(modifiers);\n if (bindings === undefined) {\n bindings = new Map();\n compiledKeymap[platform].set(modifiers, bindings);\n }\n bindings.set(shortcutKey, command);\n }\n }\n }\n compiledEditorKeymaps.set(keymap, compiledKeymap);\n }\n return compiledKeymap;\n}\n\nexport function resolveEditorCommandFromKeyboardEvent(\n event: KeyboardEvent,\n keymap?: EditorKeymap,\n isMac: boolean = isMacLike()\n): EditorCommand | undefined {\n const eventKey =\n event.key === ' '\n ? 'Space'\n : event.key.length === 1\n ? event.key.toLowerCase()\n : event.key;\n const code = event.code ?? '';\n const codeKey =\n code.startsWith('Key') && code.length === 4\n ? (code.slice(3).toLowerCase() as KeyboardKey)\n : code.startsWith('Digit') && code.length === 6\n ? (code.slice(5) as KeyboardKey)\n : keyboardCodeKeys[code];\n const platform = isMac\n ? 'mac'\n : globalThis.navigator?.platform.includes('Linux') === true\n ? 'linux'\n : 'windows';\n const modifiers =\n (event.altKey ? 1 : 0) |\n (event.ctrlKey ? 2 : 0) |\n (event.metaKey ? 4 : 0) |\n (event.shiftKey ? 8 : 0);\n\n if (keymap !== undefined) {\n const bindings = getCompiledEditorKeymap(keymap)[platform].get(modifiers);\n const command =\n bindings?.get(eventKey as KeyboardKey) ??\n (codeKey === undefined ? undefined : bindings?.get(codeKey));\n if (command !== undefined) {\n return command;\n }\n }\n\n const bindings = compiledDefaultKeymap[platform].get(modifiers);\n return (\n bindings?.get(eventKey as KeyboardKey) ??\n (codeKey === undefined ? undefined : bindings?.get(codeKey))\n );\n}\n\n// Detects the native \"find again\" shortcut (cmd/ctrl+g for next, adding shift\n// for previous) so the find/replace panel can override the browser default and\n// step through matches. macOS can emit a dead key for the physical G, so fall\n// back to event.code like the cmd+f handling above.\nexport function resolveFindAgainShortcut(\n event: KeyboardEvent,\n isMac: boolean = isMacLike()\n): 'next' | 'previous' | undefined {\n if (event.altKey) {\n return undefined;\n }\n if (!isPrimaryModifier(event, isMac)) {\n return undefined;\n }\n const normalizedKey =\n event.key.length === 1 ? event.key.toLowerCase() : event.key;\n if (normalizedKey === 'g' || event.code === 'KeyG') {\n return event.shiftKey ? 'previous' : 'next';\n }\n return undefined;\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { DiffLineAnnotation, EditorSelection } from "../types.js";
2
- import { ResolvedTextEdit, TextDocument } from "./textDocument.js";
1
+ import { DiffLineAnnotation, EditorSelection, ResolvedTextEdit } from "../types.js";
2
+ import { TextDocument } from "./textDocument.js";
3
3
 
4
4
  //#region src/editor/editStack.d.ts
5
5
  type EditStackCoalescingMode = 1 | 2 | 3;
@@ -1 +1 @@
1
- {"version":3,"file":"editStack.d.ts","names":[],"sources":["../../src/editor/editStack.ts"],"mappings":";;;;KASK,uBAAA;;UAGY,cAAA;EAHW;EAK1B,YAAA,EAAc,gBAAA;EALY;EAO1B,YAAA,EAAc,gBAAA;EAJC;EAMf,aAAA;EAN6B;EAQ7B,YAAA;EAJc;EAMd,gBAAA,GAAmB,eAAA;EAED;EAAlB,eAAA,GAAkB,eAAA;EAEM;EAAxB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA;EAEpB;EAAvB,oBAAA,GAAuB,kBAAA,CAAmB,WAAA;EAEF;EAAxC,cAAA,GAAiB,uBAAA;EAlBa;;;;;EAwB9B,YAAA;AAAA;;UAIe,gBAAA;EAhBf;EAkBA,UAAU;AAAA;;cAIC,SAAA;EAAA;EAMX,WAAA,CAAY,OAAA,GAAU,gBAAA;EAAA,IAOlB,OAAA;EAAA,IAIA,OAAA;EAjCa;EAsCjB,KAAA;EAhCY;EAuCZ,SAAA;EAnCe;EAwCf,IAAA,CAAK,KAAA,EAAO,cAAA,CAAe,WAAA;;EAU3B,0BAAA,CAA2B,UAAA,EAAY,eAAA;EAhD7B;EA0DV,0BAAA,CACE,qBAAA,EAAuB,kBAAA,CAAmB,WAAA,KAC1C,oBAAA,EAAsB,kBAAA,CAAmB,WAAA;EAxDvB;EAkEpB,QAAA,IAAY,cAAA,CAAe,WAAA;EA5DL;EAiEtB,qBAAA,IAAyB,cAAA,CAAe,WAAA;EArC5B;EA0CZ,eAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,WAAA;EArBM;EAgC5C,aAAA,IAAiB,cAAA,CAAe,WAAA;EA/BW;EAyC3C,aAAA,IAAiB,cAAA,CAAe,WAAA;AAAA;AAAA,iBAUlB,oBAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,aAAA,EAAe,gBAAA,IACf,aAAA,UACA,YAAA,UACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA,KAC3C,oBAAA,GAAuB,kBAAA,CAAmB,WAAA,MACzC,cAAA,CAAe,WAAA;;;;;;iBAmEF,4BAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,eAC9B,SAAA,EAAW,cAAA,CAAe,WAAA;;iBA8FZ,wBAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,GAC9B,SAAA,EAAW,cAAA,CAAe,WAAA,IACzB,cAAA,CAAe,WAAA"}
1
+ {"version":3,"file":"editStack.d.ts","names":[],"sources":["../../src/editor/editStack.ts"],"mappings":";;;;KAaK,uBAAA;;UAGY,cAAA;EAHW;EAK1B,YAAA,EAAc,gBAAA;EALY;EAO1B,YAAA,EAAc,gBAAA;EAJC;EAMf,aAAA;EAN6B;EAQ7B,YAAA;EAJc;EAMd,gBAAA,GAAmB,eAAA;EAED;EAAlB,eAAA,GAAkB,eAAA;EAEM;EAAxB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA;EAEpB;EAAvB,oBAAA,GAAuB,kBAAA,CAAmB,WAAA;EAEF;EAAxC,cAAA,GAAiB,uBAAA;EAlBa;;;;;EAwB9B,YAAA;AAAA;;UAIe,gBAAA;EAhBf;EAkBA,UAAU;AAAA;;cAIC,SAAA;EAAA;EAMX,WAAA,CAAY,OAAA,GAAU,gBAAA;EAAA,IAOlB,OAAA;EAAA,IAIA,OAAA;EAjCa;EAsCjB,KAAA;EAhCY;EAuCZ,SAAA;EAnCe;EAwCf,IAAA,CAAK,KAAA,EAAO,cAAA,CAAe,WAAA;;EAU3B,0BAAA,CAA2B,UAAA,EAAY,eAAA;EAhD7B;EA0DV,0BAAA,CACE,qBAAA,EAAuB,kBAAA,CAAmB,WAAA,KAC1C,oBAAA,EAAsB,kBAAA,CAAmB,WAAA;EAxDvB;EAkEpB,QAAA,IAAY,cAAA,CAAe,WAAA;EA5DL;EAiEtB,qBAAA,IAAyB,cAAA,CAAe,WAAA;EArC5B;EA0CZ,eAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,WAAA;EArBM;EAgC5C,aAAA,IAAiB,cAAA,CAAe,WAAA;EA/BW;EAyC3C,aAAA,IAAiB,cAAA,CAAe,WAAA;AAAA;AAAA,iBAUlB,oBAAA,cACd,YAAA,EAAc,YAAA,CAAa,WAAA,GAC3B,aAAA,EAAe,gBAAA,IACf,aAAA,UACA,YAAA,UACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,qBAAA,GAAwB,kBAAA,CAAmB,WAAA,KAC3C,oBAAA,GAAuB,kBAAA,CAAmB,WAAA,MACzC,cAAA,CAAe,WAAA;;;;;;iBAmEF,4BAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,eAC9B,SAAA,EAAW,cAAA,CAAe,WAAA;;iBA8FZ,wBAAA,cACd,aAAA,EAAe,cAAA,CAAe,WAAA,GAC9B,SAAA,EAAW,cAAA,CAAe,WAAA,IACzB,cAAA,CAAe,WAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack","#canCoalesce"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type { DiffLineAnnotation, EditorSelection } from '../types';\nimport type { ResolvedTextEdit, TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\nconst COALESCING_MODE_INSERT = 1;\nconst COALESCING_MODE_BACKSPACE = 2;\nconst COALESCING_MODE_DELETE = 3;\ntype EditStackCoalescingMode = 1 | 2 | 3;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /** Input mode inferred from the edit or retained by a coalesced run. */\n coalescingMode?: EditStackCoalescingMode;\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n #canCoalesce = false;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n this.#canCoalesce = false;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n this.#canCoalesce = true;\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Returns the last undo entry only while its coalescing group is open. */\n peekUndoForCoalescing(): EditStackEntry<LAnnotation> | undefined {\n return this.#canCoalesce ? this.peekUndo() : undefined;\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n this.#canCoalesce = true;\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n if (selectionsBefore?.length === forwardEdits.length) {\n const caretOffsets: number[] = [];\n for (const selection of selectionsBefore) {\n if (\n selection.start.line !== selection.end.line ||\n selection.start.character !== selection.end.character\n ) {\n break;\n }\n caretOffsets.push(textDocument.offsetAt(selection.start));\n }\n if (caretOffsets.length === forwardEdits.length) {\n caretOffsets.sort((a, b) => a - b);\n let isBackspace = true;\n let isDelete = true;\n for (let i = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n if (edit.text.length > 0 || edit.start === edit.end) {\n isBackspace = false;\n isDelete = false;\n break;\n }\n isBackspace &&= caretOffsets[i] === edit.end;\n isDelete &&= caretOffsets[i] === edit.start;\n }\n coalescingMode = isBackspace\n ? COALESCING_MODE_BACKSPACE\n : isDelete\n ? COALESCING_MODE_DELETE\n : undefined;\n }\n }\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n // Merge only when the next insert starts exactly where the previous\n // inserted text ends, in after-edit offsets. `previousInverse.end` marks\n // that point, since undo replaces exactly the inserted range. A\n // base-offset check can't tell this apart from a caret that jumped to the\n // left edge: both edges of a pure insert map back to the same base offset,\n // so typing \"a\" then \"b\" at the left edge would merge as \"ab\" while the\n // buffer holds \"ba\", corrupting redo.\n if (nextForward.start !== previousInverse.end) {\n return false;\n }\n mode ??= COALESCING_MODE_INSERT;\n if (\n mode !== COALESCING_MODE_INSERT ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== COALESCING_MODE_INSERT) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== COALESCING_MODE_INSERT)\n ) {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n let nextMode: EditStackCoalescingMode;\n if (mappedNextStart === previousForward.end) {\n nextMode = COALESCING_MODE_DELETE;\n } else if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n } else {\n nextMode = COALESCING_MODE_BACKSPACE;\n }\n mode ??= nextMode;\n if (\n mode !== nextMode ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== nextMode) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== nextMode)\n ) {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n coalescingMode ??= COALESCING_MODE_INSERT;\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n coalescingMode ??= COALESCING_MODE_DELETE;\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n coalescingMode ??= COALESCING_MODE_BACKSPACE;\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"editStack.js","names":["#maxEntries","#undoStack","#redoStack","#canCoalesce"],"sources":["../../src/editor/editStack.ts"],"sourcesContent":["import type {\n DiffLineAnnotation,\n EditorSelection,\n ResolvedTextEdit,\n} from '../types';\nimport type { TextDocument } from './textDocument';\n\n/** Largest number of undo or redo entries kept; oldest entries drop first once exceeded. */\nconst DEFAULT_EDIT_STACK_MAX_ENTRIES = 100;\n\nconst COALESCING_MODE_INSERT = 1;\nconst COALESCING_MODE_BACKSPACE = 2;\nconst COALESCING_MODE_DELETE = 3;\ntype EditStackCoalescingMode = 1 | 2 | 3;\n\n/** An entry in the edit stack. */\nexport interface EditStackEntry<LAnnotation> {\n /** Forward offset edits from the entry's base text to its final text. */\n forwardEdits: ResolvedTextEdit[];\n /** Inverse offset edits from the entry's final text back to its base text. */\n inverseEdits: ResolvedTextEdit[];\n /** Document version before the entry is applied. */\n versionBefore: number;\n /** Document version after the entry is applied. */\n versionAfter: number;\n /** Selection before the transaction. */\n selectionsBefore?: EditorSelection[];\n /** Selection after the transaction. */\n selectionsAfter?: EditorSelection[];\n /** Line annotations before the transaction. */\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[];\n /** Line annotations after the transaction. */\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[];\n /** Input mode inferred from the edit or retained by a coalesced run. */\n coalescingMode?: EditStackCoalescingMode;\n /**\n * When `true`, this entry is its own undo step and never merges with the\n * entry before or after it. Set for paste and cut, which otherwise look like\n * typing and would merge into the previous keystroke.\n */\n undoBoundary?: boolean;\n}\n\n/** Options for the edit stack. */\nexport interface EditStackOptions {\n /** The maximum number of entries to keep in the undo stack. */\n maxEntries?: number;\n}\n\n/** A stack of edit entries. */\nexport class EditStack<LAnnotation> {\n #undoStack: EditStackEntry<LAnnotation>[] = [];\n #redoStack: EditStackEntry<LAnnotation>[] = [];\n #maxEntries: number;\n #canCoalesce = false;\n\n constructor(options?: EditStackOptions) {\n this.#maxEntries = Math.max(\n 1,\n options?.maxEntries ?? DEFAULT_EDIT_STACK_MAX_ENTRIES\n );\n }\n\n get canUndo(): boolean {\n return this.#undoStack.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redoStack.length > 0;\n }\n\n /** Clears both the undo and redo stacks. */\n clear(): void {\n this.#undoStack.length = 0;\n this.#redoStack.length = 0;\n this.#canCoalesce = false;\n }\n\n /** Clears the redo stack. */\n clearRedo(): void {\n this.#redoStack.length = 0;\n }\n\n /** Pushes a new entry onto the undo stack. */\n push(entry: EditStackEntry<LAnnotation>): void {\n this.#undoStack.push(entry);\n this.clearRedo();\n this.#canCoalesce = true;\n if (this.#undoStack.length > this.#maxEntries) {\n this.#undoStack.shift();\n }\n }\n\n /** Sets the selections after the last undo entry. */\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.selectionsAfter = selections.map((selection) => ({\n ...selection,\n }));\n }\n }\n\n /** Sets the line annotations after the last undo entry. */\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n const lastEntry = this.#undoStack[this.#undoStack.length - 1];\n if (lastEntry !== undefined) {\n lastEntry.lineAnnotationsBefore = lineAnnotationsBefore.slice();\n lastEntry.lineAnnotationsAfter = lineAnnotationsAfter.slice();\n }\n }\n\n /** Returns the last undo entry, or `undefined` if empty. */\n peekUndo(): EditStackEntry<LAnnotation> | undefined {\n return this.#undoStack[this.#undoStack.length - 1];\n }\n\n /** Returns the last undo entry only while its coalescing group is open. */\n peekUndoForCoalescing(): EditStackEntry<LAnnotation> | undefined {\n return this.#canCoalesce ? this.peekUndo() : undefined;\n }\n\n /** Replaces the last undo entry with the given entry. */\n replaceLastUndo(entry: EditStackEntry<LAnnotation>): void {\n if (this.#undoStack.length === 0) {\n this.push(entry);\n return;\n }\n this.#undoStack[this.#undoStack.length - 1] = entry;\n this.clearRedo();\n this.#canCoalesce = true;\n }\n\n /** Moves the latest undo entry to the redo stack and returns it, or `undefined` if empty. */\n popUndoToRedo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#undoStack.pop();\n if (entry !== undefined) {\n this.#redoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n\n /** Moves the latest redo entry back to the undo stack and returns it, or `undefined` if empty. */\n popRedoToUndo(): EditStackEntry<LAnnotation> | void {\n const entry = this.#redoStack.pop();\n if (entry !== undefined) {\n this.#undoStack.push(entry);\n this.#canCoalesce = false;\n return entry;\n }\n }\n}\n\nexport function createEditStackEntry<LAnnotation>(\n textDocument: TextDocument<LAnnotation>,\n resolvedEdits: ResolvedTextEdit[],\n versionBefore: number,\n versionAfter: number,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n lineAnnotationsBefore?: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter?: DiffLineAnnotation<LAnnotation>[]\n): EditStackEntry<LAnnotation> {\n const forwardEdits = [...resolvedEdits].sort((a, b) => a.start - b.start);\n const inverseEdits: ResolvedTextEdit[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n if (selectionsBefore?.length === forwardEdits.length) {\n const caretOffsets: number[] = [];\n for (const selection of selectionsBefore) {\n if (\n selection.start.line !== selection.end.line ||\n selection.start.character !== selection.end.character\n ) {\n break;\n }\n caretOffsets.push(textDocument.offsetAt(selection.start));\n }\n if (caretOffsets.length === forwardEdits.length) {\n caretOffsets.sort((a, b) => a - b);\n let isBackspace = true;\n let isDelete = true;\n for (let i = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n if (edit.text.length > 0 || edit.start === edit.end) {\n isBackspace = false;\n isDelete = false;\n break;\n }\n isBackspace &&= caretOffsets[i] === edit.end;\n isDelete &&= caretOffsets[i] === edit.start;\n }\n coalescingMode = isBackspace\n ? COALESCING_MODE_BACKSPACE\n : isDelete\n ? COALESCING_MODE_DELETE\n : undefined;\n }\n }\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const replacedText = textDocument.getTextSlice(edit.start, edit.end);\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedText,\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return {\n forwardEdits: forwardEdits.map((edit) => ({ ...edit })),\n inverseEdits: inverseEdits,\n versionBefore,\n versionAfter,\n selectionsBefore: selectionsBefore?.map((selection) => ({\n ...selection,\n })),\n selectionsAfter: selectionsAfter?.map((selection) => ({ ...selection })),\n lineAnnotationsBefore: lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\n/** Determines if the change matches following modes:\n * - 'insert': simple typing\n * - 'backspace': backward delete\n * - 'delete': forward delete\n */\nexport function shouldCoalesceEditStackEntry<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation> | undefined,\n nextEntry: EditStackEntry<LAnnotation>\n): boolean {\n if (\n previousEntry === undefined ||\n previousEntry.undoBoundary === true ||\n nextEntry.undoBoundary === true ||\n previousEntry.forwardEdits.length === 0 ||\n previousEntry.forwardEdits.length !== previousEntry.inverseEdits.length ||\n previousEntry.forwardEdits.length !== nextEntry.forwardEdits.length ||\n nextEntry.forwardEdits.length !== nextEntry.inverseEdits.length\n ) {\n return false;\n }\n let mode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n const previousWasInsert =\n previousForward.start <= previousForward.end &&\n previousForward.text.length > 0 &&\n !previousForward.text.includes('\\n') &&\n !previousInverse.text.includes('\\n');\n const nextIsInsert =\n nextForward.start === nextForward.end &&\n nextForward.text.length > 0 &&\n !nextForward.text.includes('\\n') &&\n nextInverse.text.length === 0;\n if (previousWasInsert && nextIsInsert) {\n // Merge only when the next insert starts exactly where the previous\n // inserted text ends, in after-edit offsets. `previousInverse.end` marks\n // that point, since undo replaces exactly the inserted range. A\n // base-offset check can't tell this apart from a caret that jumped to the\n // left edge: both edges of a pure insert map back to the same base offset,\n // so typing \"a\" then \"b\" at the left edge would merge as \"ab\" while the\n // buffer holds \"ba\", corrupting redo.\n if (nextForward.start !== previousInverse.end) {\n return false;\n }\n mode ??= COALESCING_MODE_INSERT;\n if (\n mode !== COALESCING_MODE_INSERT ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== COALESCING_MODE_INSERT) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== COALESCING_MODE_INSERT)\n ) {\n return false;\n }\n continue;\n }\n const previousWasDelete =\n previousForward.text.length === 0 &&\n previousForward.end > previousForward.start &&\n previousInverse.text.length > 0;\n const nextIsDelete =\n nextForward.text.length === 0 &&\n nextForward.end > nextForward.start &&\n nextInverse.text.length > 0;\n if (previousWasDelete && nextIsDelete) {\n let nextMode: EditStackCoalescingMode;\n if (mappedNextStart === previousForward.end) {\n nextMode = COALESCING_MODE_DELETE;\n } else if (\n mappedNextStart + (nextForward.end - nextForward.start) !==\n previousForward.start\n ) {\n return false;\n } else {\n nextMode = COALESCING_MODE_BACKSPACE;\n }\n mode ??= nextMode;\n if (\n mode !== nextMode ||\n (previousEntry.coalescingMode !== undefined &&\n previousEntry.coalescingMode !== nextMode) ||\n (nextEntry.coalescingMode !== undefined &&\n nextEntry.coalescingMode !== nextMode)\n ) {\n return false;\n }\n continue;\n }\n return false;\n }\n return mode !== undefined;\n}\n\n/** Coalesce edit stack entries for simple typing and single-character deletes. */\nexport function coalesceEditStackEntries<LAnnotation>(\n previousEntry: EditStackEntry<LAnnotation>,\n nextEntry: EditStackEntry<LAnnotation>\n): EditStackEntry<LAnnotation> {\n const forwardEdits: ResolvedTextEdit[] = [];\n const replacedTexts: string[] = [];\n let coalescingMode: EditStackCoalescingMode | undefined;\n for (let i = 0; i < previousEntry.forwardEdits.length; i++) {\n const previousForward = previousEntry.forwardEdits[i];\n const previousInverse = previousEntry.inverseEdits[i];\n const nextForward = nextEntry.forwardEdits[i];\n const nextInverse = nextEntry.inverseEdits[i];\n const mappedNextStart = mapOffsetAfterForwardBatchToBefore(\n nextForward.start,\n previousEntry.forwardEdits\n );\n\n if (previousForward.text.length > 0) {\n coalescingMode ??= COALESCING_MODE_INSERT;\n forwardEdits.push({\n start: previousForward.start,\n end: previousForward.end,\n text: previousForward.text + nextForward.text,\n });\n replacedTexts.push(previousInverse.text);\n continue;\n }\n\n if (mappedNextStart === previousForward.end) {\n coalescingMode ??= COALESCING_MODE_DELETE;\n forwardEdits.push({\n start: previousForward.start,\n end: mappedNextStart + (nextForward.end - nextForward.start),\n text: '',\n });\n replacedTexts.push(previousInverse.text + nextInverse.text);\n continue;\n }\n\n coalescingMode ??= COALESCING_MODE_BACKSPACE;\n forwardEdits.push({\n start: Math.min(previousForward.start, mappedNextStart),\n end: previousForward.end,\n text: '',\n });\n replacedTexts.push(nextInverse.text + previousInverse.text);\n }\n\n return {\n forwardEdits,\n inverseEdits: buildInverseEditsFromReplacedTexts(\n forwardEdits,\n replacedTexts\n ),\n versionBefore: previousEntry.versionBefore,\n versionAfter: nextEntry.versionAfter,\n selectionsBefore: previousEntry.selectionsBefore?.slice(),\n selectionsAfter: nextEntry.selectionsAfter?.slice(),\n lineAnnotationsBefore: previousEntry.lineAnnotationsBefore?.slice(),\n lineAnnotationsAfter: nextEntry.lineAnnotationsAfter?.slice(),\n coalescingMode,\n };\n}\n\nfunction buildInverseEditsFromReplacedTexts(\n forwardEdits: readonly ResolvedTextEdit[],\n replacedTexts: readonly string[]\n): ResolvedTextEdit[] {\n const inverseEdits: ResolvedTextEdit[] = [];\n for (let i = 0, offsetDelta = 0; i < forwardEdits.length; i++) {\n const edit = forwardEdits[i];\n const startAfterEdit = edit.start + offsetDelta;\n inverseEdits.push({\n start: startAfterEdit,\n end: startAfterEdit + edit.text.length,\n text: replacedTexts[i],\n });\n offsetDelta += edit.text.length - (edit.end - edit.start);\n }\n return inverseEdits;\n}\n\nfunction mapOffsetAfterForwardBatchToBefore(\n offsetAfter: number,\n forwardEdits: readonly ResolvedTextEdit[]\n): number {\n let offset = offsetAfter;\n for (const edit of forwardEdits) {\n const oldLength = edit.end - edit.start;\n const newLength = edit.text.length;\n const delta = newLength - oldLength;\n if (offset < edit.start) {\n continue;\n }\n if (offset >= edit.start + newLength) {\n offset -= delta;\n continue;\n }\n offset = edit.start + Math.min(offset - edit.start, oldLength);\n }\n return offset;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { DiffLineAnnotation, DiffsEditableComponent, DiffsEditor, EditableInstance, EditorState, FileContents, LineAnnotation, Range, TextEdit } from "../types.js";
1
+ import { DiffLineAnnotation, DiffsEditableComponent, DiffsEditor, EditableInstance, EditorChangeEvent, EditorState, FileContents, LineAnnotation, Range, TextEdit } from "../types.js";
2
+ import { EditorKeymap } from "./command.js";
2
3
  import { LanguageConfigMap } from "./languages.js";
3
4
  import { Marker } from "./marker.js";
4
5
  import { AutoSurround } from "./selection.js";
@@ -9,6 +10,8 @@ import { PersistStateStorage } from "./stateStorage.js";
9
10
  interface EditorOptions<LAnnotation> {
10
11
  /** The maximum number of entries to keep in the undo stack. */
11
12
  historyMaxEntries?: number;
13
+ /** Custom keymap groups checked before defaults; later groups take precedence. */
14
+ keymap?: EditorKeymap;
12
15
  /**
13
16
  * Preserve each file's document and item-local editor state when switching files.
14
17
  * Every editable file must provide a unique, stable `cacheKey`.
@@ -48,7 +51,7 @@ interface EditorOptions<LAnnotation> {
48
51
  /** Callback when the editor is attached to a file. */
49
52
  onAttach?: (editor: Editor<LAnnotation>, fileInstance: DiffsEditableComponent<LAnnotation>) => void;
50
53
  /** Callback when the editor document changes. */
51
- onChange?: (file: FileContents, lineAnnotations?: LineAnnotation<LAnnotation>[] | DiffLineAnnotation<LAnnotation>[]) => void;
54
+ onChange?: (file: FileContents, lineAnnotations: LineAnnotation<LAnnotation>[] | DiffLineAnnotation<LAnnotation>[] | undefined, event: EditorChangeEvent<LAnnotation>) => void;
52
55
  /** Callback when the editor gains focus. */
53
56
  onFocus?: () => void;
54
57
  /** Callback when the editor loses focus. */
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;;UAyLiB,aAAA;;EAEf,iBAAA;EAF4B;;;;EAO5B,YAAA;EA4B+B;;;;EAvB/B,mBAAA,GAAsB,mBAAA;EA+BZ;EA7BV,gBAAA;EA8BgB;EA5BhB,aAAA;EAkCqB;;;;EA7BrB,YAAA,GAAe,YAAA;EA8BS;EA5BxB,qBAAA,GAAwB,iBAAA;EArBxB;;;;EA0BA,sBAAA;EAZA;;;;;EAkBA,SAAA;IACE,QAAA,GAAW,IAAA,cAAkB,OAAA;EAAA;EAAlB;EAGb,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EAFL;EAIA,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EALL;EAQlC,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,GACI,cAAA,CAAe,WAAA,MACf,kBAAA,CAAmB,WAAA;EAXpB;EAcL,OAAA;EAXU;EAaV,MAAA;EAEA,OAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,YAAY;EAjBpD;EAmBF,UAAA;EAfQ;EAiBR,SAAA;EAfM;EAiBN,MAAA;AAAA;AAAA,cAmBW,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAkJtD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAkClC,IAAA,WAAe,sBAAA,CAAuB,WAAA,GACpC,YAAA,EAAc,gBAAA,CAAiB,CAAA;EArN1B;AAAA;AAGT;;;;;;;;EA4OE,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EAtOxB;EAAA,IAySF,OAAA;EAtRa;EAAA,IA2Rb,OAAA;EA3RkD;EAgStD,IAAA;EA9IqB;EAmJrB,IAAA;EAIA,OAAA,IAAW,YAAA;EAcX,OAAA;EAIA,QAAA,IAAY,WAAA;EAaZ,QAAA;IAAW,UAAA;IAAY;EAAA,GAAQ,WAAA;EAgB/B,aAAA,CACE,UAAA,GAAa,KAAA;IAAU,SAAA;EAAA;EAoCzB,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,kBAAA;EAmChB,IAAA;EAOA,OAAA,CAAQ,OAAA;EA3GO;EA8Lf,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,YAAA;EA7HnB;EAyJhB,+BAAA;EA5BmC;EAuCnC,+BAAA;EAekB;EAAlB,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
1
+ {"version":3,"file":"editor.d.ts","names":[],"sources":["../../src/editor/editor.ts"],"mappings":";;;;;;;;;UAwLiB,aAAA;;EAEf,iBAAA;EAF4B;EAI5B,MAAA,GAAS,YAAA;EAAA;;;;EAKT,YAAA;EAgCkC;;;;EA3BlC,mBAAA,GAAsB,mBAAA;EAgCiB;EA9BvC,gBAAA;EAkCQ;EAhCR,aAAA;EAkCM;;;;EA7BN,YAAA,GAAe,YAAA;EAgCW;EA9B1B,qBAAA,GAAwB,iBAAA;EAzBK;;;;EA8B7B,sBAAA;EAhBA;;;;;EAsBA,SAAA;IACE,QAAA,GAAW,IAAA,cAAkB,OAAA;EAAA;EAP/B;EAUA,qBAAA,IACE,OAAA,EAAS,sBAAA,CAAuB,WAAA,MAC7B,WAAA;EALH;EAOF,QAAA,IACE,MAAA,EAAQ,MAAA,CAAO,WAAA,GACf,YAAA,EAAc,sBAAA,CAAuB,WAAA;EATR;EAY/B,QAAA,IACE,IAAA,EAAM,YAAA,EACN,eAAA,EACI,cAAA,CAAe,WAAA,MACf,kBAAA,CAAmB,WAAA,iBAEvB,KAAA,EAAO,iBAAA,CAAkB,WAAA;EAdhB;EAiBX,OAAA;EAjBE;EAmBF,MAAA;EAEA,OAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,YAAY;EApBpD;EAsBF,UAAA;EArBuC;EAuBvC,SAAA;EApBA;EAsBA,MAAA;AAAA;AAAA,cAmBW,MAAA,yBAA+B,WAAA,CAAY,WAAA;EAAA;EAkJtD,WAAA,CAAY,OAAA,GAAS,aAAA,CAAc,WAAA;EAInC,UAAA,CAAW,OAAA,EAAS,aAAA,CAAc,WAAA;EAkClC,IAAA,WAAe,sBAAA,CAAuB,WAAA,GACpC,YAAA,EAAc,gBAAA,CAAiB,CAAA;EA5NxB;;;;;;;AAOF;AAGT;;EA4OE,UAAA,CAAW,KAAA,EAAO,QAAA,IAAY,aAAA;EA5OwB;EAAA,IA+SlD,OAAA;EA7SJ;EAAA,IAkTI,OAAA;EA9SJ;EAmTA,IAAA;EAnTM;EAwTN,IAAA;EAIA,OAAA,IAAW,YAAA;EAcX,OAAA;EAIA,QAAA,IAAY,WAAA;EAaZ,QAAA;IAAW,UAAA;IAAY;EAAA,GAAQ,WAAA;EAgB/B,aAAA,CACE,UAAA,GAAa,KAAA;IAAU,SAAA;EAAA;EAoCzB,UAAA,CAAW,OAAA,EAAS,MAAA;EA6BpB,KAAA,CAAM,OAAA,GAAU,kBAAA;EAmChB,IAAA;EAOA,OAAA,CAAQ,OAAA;EAjPU;EAoUlB,aAAA,CAAc,IAAA,EAAM,YAAA,GAAe,YAAA;EA5NvB;EAwPZ,+BAAA;EA3OuB;EAsPvB,+BAAA;EArOe;EAoPf,gBAAA,EAAkB,WAAA,CAAY,WAAA;AAAA"}
@@ -1,7 +1,8 @@
1
1
  import { dequeueRender, queueRender } from "../managers/UniversalRenderingManager.js";
2
2
  import { isGutterUtilityPath } from "../utils/isGutterUtilityPath.js";
3
3
  import { getFiletypeFromFileName } from "../utils/getFiletypeFromFileName.js";
4
- import { isMoveCursorShortcut, isPrimaryModifier, isSafari } from "./platform.js";
4
+ import { isSafari } from "../utils/platform.js";
5
+ import { isMoveCursorShortcut, isPrimaryModifier } from "./platform.js";
5
6
  import { resolveEditorCommandFromKeyboardEvent, resolveFindAgainShortcut } from "./command.js";
6
7
  import editor_default from "./editor2.js";
7
8
  import { EditStack } from "./editStack.js";
@@ -864,24 +865,27 @@ var Editor = class {
864
865
  }
865
866
  }, { passive: true }),
866
867
  addEventListener(contentEl, "keydown", (e) => {
867
- if (e.key === "Escape") {
868
- e.preventDefault();
869
- this.#searchPanel?.close();
870
- this.#searchPanel = void 0;
871
- this.#retainSearchPanelFocus = false;
872
- this.#selectionAction?.cleanup();
873
- this.#selectionAction = void 0;
874
- this.#runCommand("simplifySelection");
875
- return;
876
- }
877
868
  if (!targetIsContentElement(e)) return;
878
869
  this.#canMountSelectionAction = true;
879
870
  this.#suppressNativeSelectionSync = false;
871
+ const command = resolveEditorCommandFromKeyboardEvent(e, this.#options.keymap);
872
+ if (command !== void 0) {
873
+ e.preventDefault();
874
+ if (command === "simplifySelection") {
875
+ this.#searchPanel?.close();
876
+ this.#searchPanel = void 0;
877
+ this.#retainSearchPanelFocus = false;
878
+ this.#selectionAction?.cleanup();
879
+ this.#selectionAction = void 0;
880
+ }
881
+ this.#runCommand(command);
882
+ return;
883
+ }
880
884
  const mvShortcut = isMoveCursorShortcut(e);
881
885
  const textDocument = this.#textDocument;
882
886
  if (this.#selections !== void 0 && this.#selections.length > 0 && mvShortcut !== void 0 && textDocument !== void 0) {
883
887
  const cursorMoveOptions = {
884
- getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineText(line) : void 0,
888
+ getSoftLineOffsets: this.#isWrap ? (line) => this.#wrapLineTextOrWholeLine(line) : void 0,
885
889
  resolveRenderableLine: this.#resolveRenderableLine
886
890
  };
887
891
  if (e.shiftKey) this.#updateSelections(mapSelectionShift(textDocument, this.#selections, mvShortcut, cursorMoveOptions));
@@ -909,11 +913,6 @@ var Editor = class {
909
913
  return;
910
914
  }
911
915
  }
912
- const command = resolveEditorCommandFromKeyboardEvent(e);
913
- if (command !== void 0) {
914
- e.preventDefault();
915
- this.#runCommand(command);
916
- }
917
916
  }),
918
917
  addEventListener(contentEl, "copy", (e) => {
919
918
  if (!targetIsContentElement(e)) return;
@@ -2145,7 +2144,7 @@ var Editor = class {
2145
2144
  }
2146
2145
  }
2147
2146
  #renderWrappedSelection(renderCtx, line, lineText, startChar, endChar, isLastLine, type, extraDataset) {
2148
- const wrapOffsets = this.#wrapLineText(line);
2147
+ const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
2149
2148
  const segmentCount = wrapOffsets.length - 1;
2150
2149
  const offsetLeft = this.#getGutterWidth() + this.#metrics.ch + (this.#activeContentOffset?.left ?? 0);
2151
2150
  for (let wrapLine = 0; wrapLine < segmentCount; wrapLine++) {
@@ -2342,17 +2341,17 @@ var Editor = class {
2342
2341
  top,
2343
2342
  bottom: top + popoverHeight,
2344
2343
  left,
2345
- anchorTop
2344
+ anchorTop,
2345
+ rowTop
2346
2346
  };
2347
2347
  };
2348
2348
  const preferredGeometry = candidateGeometry(preferred);
2349
- const fallbackGeometry = candidateGeometry(fallback);
2350
2349
  const lineCount = textDocument.lineCount;
2351
2350
  const atDocumentEdge = isBackward ? head.line < 3 : head.line >= lineCount - 3;
2352
- const canUseFallback = this.#isLineVisible(fallback.anchor.line);
2351
+ const fallbackGeometry = this.#isLineVisible(fallback.anchor.line) ? candidateGeometry(fallback) : void 0;
2353
2352
  const popoverManager = this.#getPopoverManager();
2354
2353
  const viewport = popoverManager.getPlacementBounds();
2355
- const useFallback = canUseFallback && popoverManager.choosePlacement({
2354
+ const useFallback = fallbackGeometry !== void 0 && popoverManager.choosePlacement({
2356
2355
  preferred: preferredGeometry,
2357
2356
  fallback: fallbackGeometry,
2358
2357
  viewport,
@@ -2360,10 +2359,13 @@ var Editor = class {
2360
2359
  atDocumentEdge,
2361
2360
  placementKey: SELECTION_ACTION_POPOVER_PLACEMENT_KEY
2362
2361
  }) === "fallback";
2363
- if (!canUseFallback) popoverManager.setPlacement("preferred", SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
2362
+ if (fallbackGeometry === void 0) popoverManager.setPlacement("preferred", SELECTION_ACTION_POPOVER_PLACEMENT_KEY);
2364
2363
  const { placeAbove } = useFallback ? fallback : preferred;
2365
- const { left, anchorTop } = useFallback ? fallbackGeometry : preferredGeometry;
2366
- this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove, viewport);
2364
+ const { left, anchorTop } = useFallback && fallbackGeometry !== void 0 ? fallbackGeometry : preferredGeometry;
2365
+ const selectionTop = isBackward ? preferredGeometry.rowTop : fallbackGeometry?.rowTop ?? -Infinity;
2366
+ const selectionBottom = (isBackward ? fallbackGeometry?.rowTop ?? Infinity : preferredGeometry.rowTop) + (primarySelection.end.line > primarySelection.start.line && primarySelection.end.character === 0 ? 0 : lineHeight);
2367
+ const isSelectionVisible = viewport === void 0 || selectionBottom > viewport.top && selectionTop < viewport.bottom;
2368
+ this.#selectionAction.reposition(left, anchorTop, this.#getGutterWidth(), placeAbove, isSelectionVisible, viewport);
2367
2369
  }
2368
2370
  #openSearchPanel(mode) {
2369
2371
  if (this.#searchPanel !== void 0) {
@@ -2528,7 +2530,7 @@ var Editor = class {
2528
2530
  const textDocument = this.#textDocument;
2529
2531
  if (selections === void 0 || textDocument === void 0) return;
2530
2532
  const { nextSelections, change } = applyDeleteSoftLineBackwardToSelections(textDocument, selections, this.#isWrap ? (line, character) => {
2531
- const wrapOffsets = this.#wrapLineText(line);
2533
+ const wrapOffsets = this.#wrapLineTextOrWholeLine(line);
2532
2534
  for (let w = 0; w + 1 < wrapOffsets.length; w++) {
2533
2535
  const segmentStart = wrapOffsets[w];
2534
2536
  const segmentEnd = wrapOffsets[w + 1];
@@ -2562,7 +2564,14 @@ var Editor = class {
2562
2564
  #applyChange(change, newSelections, newLineAnnotations, options) {
2563
2565
  const fileRef = this.getFile();
2564
2566
  const onChange = this.#options.onChange;
2565
- if (fileRef !== void 0 && onChange !== void 0) onChange(fileRef, newLineAnnotations ?? this.#lineAnnotations);
2567
+ if (fileRef !== void 0 && onChange !== void 0) {
2568
+ const lineAnnotations = newLineAnnotations ?? this.#lineAnnotations;
2569
+ onChange(fileRef, lineAnnotations, {
2570
+ changes: change.changes,
2571
+ file: fileRef,
2572
+ lineAnnotations
2573
+ });
2574
+ }
2566
2575
  if (change.lineDelta !== 0 || this.#isWrap) {
2567
2576
  for (const line of this.#lineYCache.keys()) if (line >= change.startLine) this.#lineYCache.delete(line);
2568
2577
  }
@@ -2704,6 +2713,7 @@ var Editor = class {
2704
2713
  const contentWidth = this.#getContentWidth();
2705
2714
  if (2 * this.#metrics.ch + this.#metrics.measureTextWidth(lineText) > contentWidth) {
2706
2715
  const wrapOffsets = this.#wrapLineText(line);
2716
+ if (wrapOffsets == null) return [left + (this.#activeContentOffset?.left ?? 0), 0];
2707
2717
  for (let w = 0; w + 1 < wrapOffsets.length; w++) {
2708
2718
  const segmentStart = wrapOffsets[w];
2709
2719
  if (boundedCharacter <= wrapOffsets[w + 1]) {
@@ -2741,6 +2751,8 @@ var Editor = class {
2741
2751
  this.#wrapLineOffsetsCache.set(line, offsets);
2742
2752
  return offsets;
2743
2753
  }
2754
+ const contentElement = this.#contentElement;
2755
+ if (contentElement == null || !contentElement.isConnected) return;
2744
2756
  const div = h("div", {
2745
2757
  style: {
2746
2758
  position: "absolute",
@@ -2757,13 +2769,15 @@ var Editor = class {
2757
2769
  tabSize: this.#metrics.tabSize.toString()
2758
2770
  },
2759
2771
  textContent: lineText
2760
- }, this.#contentElement);
2772
+ }, contentElement);
2761
2773
  const textNode = div.firstChild;
2762
2774
  const range = document.createRange();
2763
2775
  const starts = [];
2764
2776
  try {
2777
+ const divRect = div.getBoundingClientRect();
2778
+ if (divRect.width === 0) return;
2765
2779
  const unicodeOffsets = getUnicodeMeasurementOffsets(lineText);
2766
- const wrapLineStartLeft = div.getBoundingClientRect().left + this.#metrics.ch;
2780
+ const wrapLineStartLeft = divRect.left + this.#metrics.ch;
2767
2781
  const graphemeCount = unicodeOffsets === void 0 ? lineText.length : unicodeOffsets.length - 1;
2768
2782
  const graphemeStart = (index) => unicodeOffsets === void 0 ? index : unicodeOffsets[index];
2769
2783
  const measureGrapheme = (index) => {
@@ -2798,6 +2812,12 @@ var Editor = class {
2798
2812
  div.remove();
2799
2813
  }
2800
2814
  }
2815
+ #wrapLineTextOrWholeLine(line) {
2816
+ const offsets = this.#wrapLineText(line);
2817
+ if (offsets !== void 0) return offsets;
2818
+ const length = this.#textDocument?.getLineText(line)?.length ?? 0;
2819
+ return Uint32Array.of(0, length);
2820
+ }
2801
2821
  #rangeBelongsToEditor({ startContainer, endContainer }) {
2802
2822
  const contentEl = this.#contentElement;
2803
2823
  if (contentEl === void 0) return false;