@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.
- package/dist/components/CodeView.d.ts +4 -1
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +60 -26
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +1 -0
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +18 -9
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +4 -0
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +48 -21
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/VirtualizedFile.d.ts +1 -0
- package/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +3 -0
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.d.ts +1 -0
- package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +3 -0
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/edit/index.d.ts +4 -3
- package/dist/editor/command.d.ts +10 -2
- package/dist/editor/command.d.ts.map +1 -1
- package/dist/editor/command.js +113 -39
- package/dist/editor/command.js.map +1 -1
- package/dist/editor/editStack.d.ts +2 -2
- package/dist/editor/editStack.d.ts.map +1 -1
- package/dist/editor/editStack.js.map +1 -1
- package/dist/editor/editor.d.ts +5 -2
- package/dist/editor/editor.d.ts.map +1 -1
- package/dist/editor/editor.js +49 -29
- package/dist/editor/editor.js.map +1 -1
- package/dist/editor/pieceTable.d.ts +1 -2
- package/dist/editor/pieceTable.d.ts.map +1 -1
- package/dist/editor/pieceTable.js.map +1 -1
- package/dist/editor/platform.d.ts +3 -11
- package/dist/editor/platform.d.ts.map +1 -1
- package/dist/editor/platform.js +2 -28
- package/dist/editor/platform.js.map +1 -1
- package/dist/editor/searchPanel.d.ts +2 -1
- package/dist/editor/searchPanel.d.ts.map +1 -1
- package/dist/editor/searchPanel.js.map +1 -1
- package/dist/editor/selection.d.ts +2 -2
- package/dist/editor/selection.d.ts.map +1 -1
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/selectionAction.d.ts +2 -1
- package/dist/editor/selectionAction.d.ts.map +1 -1
- package/dist/editor/selectionAction.js +3 -1
- package/dist/editor/selectionAction.js.map +1 -1
- package/dist/editor/textDocument.d.ts +4 -14
- package/dist/editor/textDocument.d.ts.map +1 -1
- package/dist/editor/textDocument.js +7 -0
- package/dist/editor/textDocument.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/react/CodeView.d.ts +1 -0
- package/dist/react/CodeView.d.ts.map +1 -1
- package/dist/react/CodeView.js +9 -0
- package/dist/react/CodeView.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/ssr/index.d.ts +2 -2
- package/dist/types.d.ts +22 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/areManagedSnapshotsEqual.js +1 -1
- package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
- package/dist/utils/guardWebKitScrollDuringRebuild.d.ts +20 -0
- package/dist/utils/guardWebKitScrollDuringRebuild.d.ts.map +1 -0
- package/dist/utils/guardWebKitScrollDuringRebuild.js +34 -0
- package/dist/utils/guardWebKitScrollDuringRebuild.js.map +1 -0
- package/dist/utils/platform.d.ts +14 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +32 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/worker/wasm-B9ZqxnKj.js.map +1 -1
- package/dist/worker/worker-portable.js +13 -12
- package/dist/worker/worker-portable.js.map +1 -1
- package/package.json +1 -1
package/dist/editor/command.js
CHANGED
|
@@ -1,47 +1,121 @@
|
|
|
1
|
-
import { isMacLike
|
|
1
|
+
import { isMacLike } from "../utils/platform.js";
|
|
2
|
+
import { isPrimaryModifier } from "./platform.js";
|
|
2
3
|
//#region src/editor/command.ts
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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 {
|
|
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":";;;;
|
|
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":";;AAIA,MAAM,iCAAiC;AAEvC,MAAM,yBAAyB;AAC/B,MAAM,4BAA4B;AAClC,MAAM,yBAAyB;;AAsC/B,IAAa,YAAb,MAAoC;CAClC,aAA4C,CAAC;CAC7C,aAA4C,CAAC;CAC7C;CACA,eAAe;CAEf,YAAY,SAA4B;EACtC,KAAKA,cAAc,KAAK,IACtB,GACA,SAAS,cAAc,8BACzB;CACF;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAKC,WAAW,SAAS;CAClC;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAKC,WAAW,SAAS;CAClC;;CAGA,QAAc;EACZ,KAAKD,WAAW,SAAS;EACzB,KAAKC,WAAW,SAAS;EACzB,KAAKC,eAAe;CACtB;;CAGA,YAAkB;EAChB,KAAKD,WAAW,SAAS;CAC3B;;CAGA,KAAK,OAA0C;EAC7C,KAAKD,WAAW,KAAK,KAAK;EAC1B,KAAK,UAAU;EACf,KAAKE,eAAe;EACpB,IAAI,KAAKF,WAAW,SAAS,KAAKD,aAChC,KAAKC,WAAW,MAAM;CAE1B;;CAGA,2BAA2B,YAAqC;EAC9D,MAAM,YAAY,KAAKA,WAAW,KAAKA,WAAW,SAAS;EAC3D,IAAI,cAAc,KAAA,GAChB,UAAU,kBAAkB,WAAW,KAAK,eAAe,EACzD,GAAG,UACL,EAAE;CAEN;;CAGA,2BACE,uBACA,sBACM;EACN,MAAM,YAAY,KAAKA,WAAW,KAAKA,WAAW,SAAS;EAC3D,IAAI,cAAc,KAAA,GAAW;GAC3B,UAAU,wBAAwB,sBAAsB,MAAM;GAC9D,UAAU,uBAAuB,qBAAqB,MAAM;EAC9D;CACF;;CAGA,WAAoD;EAClD,OAAO,KAAKA,WAAW,KAAKA,WAAW,SAAS;CAClD;;CAGA,wBAAiE;EAC/D,OAAO,KAAKE,eAAe,KAAK,SAAS,IAAI,KAAA;CAC/C;;CAGA,gBAAgB,OAA0C;EACxD,IAAI,KAAKF,WAAW,WAAW,GAAG;GAChC,KAAK,KAAK,KAAK;GACf;EACF;EACA,KAAKA,WAAW,KAAKA,WAAW,SAAS,KAAK;EAC9C,KAAK,UAAU;EACf,KAAKE,eAAe;CACtB;;CAGA,gBAAoD;EAClD,MAAM,QAAQ,KAAKF,WAAW,IAAI;EAClC,IAAI,UAAU,KAAA,GAAW;GACvB,KAAKC,WAAW,KAAK,KAAK;GAC1B,KAAKC,eAAe;GACpB,OAAO;EACT;CACF;;CAGA,gBAAoD;EAClD,MAAM,QAAQ,KAAKD,WAAW,IAAI;EAClC,IAAI,UAAU,KAAA,GAAW;GACvB,KAAKD,WAAW,KAAK,KAAK;GAC1B,KAAKE,eAAe;GACpB,OAAO;EACT;CACF;AACF;AAEA,SAAgB,qBACd,cACA,eACA,eACA,cACA,kBACA,iBACA,uBACA,sBAC6B;CAC7B,MAAM,eAAe,CAAC,GAAG,aAAa,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACxE,MAAM,eAAmC,CAAC;CAC1C,IAAI;CACJ,IAAI,kBAAkB,WAAW,aAAa,QAAQ;EACpD,MAAM,eAAyB,CAAC;EAChC,KAAK,MAAM,aAAa,kBAAkB;GACxC,IACE,UAAU,MAAM,SAAS,UAAU,IAAI,QACvC,UAAU,MAAM,cAAc,UAAU,IAAI,WAE5C;GAEF,aAAa,KAAK,aAAa,SAAS,UAAU,KAAK,CAAC;EAC1D;EACA,IAAI,aAAa,WAAW,aAAa,QAAQ;GAC/C,aAAa,MAAM,GAAG,MAAM,IAAI,CAAC;GACjC,IAAI,cAAc;GAClB,IAAI,WAAW;GACf,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;IAC5C,MAAM,OAAO,aAAa;IAC1B,IAAI,KAAK,KAAK,SAAS,KAAK,KAAK,UAAU,KAAK,KAAK;KACnD,cAAc;KACd,WAAW;KACX;IACF;IACA,gBAAgB,aAAa,OAAO,KAAK;IACzC,aAAa,aAAa,OAAO,KAAK;GACxC;GACA,iBAAiB,cACb,4BACA,WACE,yBACA,KAAA;EACR;CACF;CACA,KAAK,IAAI,IAAI,GAAG,cAAc,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC7D,MAAM,OAAO,aAAa;EAC1B,MAAM,eAAe,aAAa,aAAa,KAAK,OAAO,KAAK,GAAG;EACnE,MAAM,iBAAiB,KAAK,QAAQ;EACpC,aAAa,KAAK;GAChB,OAAO;GACP,KAAK,iBAAiB,KAAK,KAAK;GAChC,MAAM;EACR,CAAC;EACD,eAAe,KAAK,KAAK,UAAU,KAAK,MAAM,KAAK;CACrD;CACA,OAAO;EACL,cAAc,aAAa,KAAK,UAAU,EAAE,GAAG,KAAK,EAAE;EACxC;EACd;EACA;EACA,kBAAkB,kBAAkB,KAAK,eAAe,EACtD,GAAG,UACL,EAAE;EACF,iBAAiB,iBAAiB,KAAK,eAAe,EAAE,GAAG,UAAU,EAAE;EACvE,uBAAuB,uBAAuB,MAAM;EACpD,sBAAsB,sBAAsB,MAAM;EAClD;CACF;AACF;;;;;;AAOA,SAAgB,6BACd,eACA,WACS;CACT,IACE,kBAAkB,KAAA,KAClB,cAAc,iBAAiB,QAC/B,UAAU,iBAAiB,QAC3B,cAAc,aAAa,WAAW,KACtC,cAAc,aAAa,WAAW,cAAc,aAAa,UACjE,cAAc,aAAa,WAAW,UAAU,aAAa,UAC7D,UAAU,aAAa,WAAW,UAAU,aAAa,QAEzD,OAAO;CAET,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,aAAa,QAAQ,KAAK;EAC1D,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,kBAAkB,mCACtB,YAAY,OACZ,cAAc,YAChB;EACA,MAAM,oBACJ,gBAAgB,SAAS,gBAAgB,OACzC,gBAAgB,KAAK,SAAS,KAC9B,CAAC,gBAAgB,KAAK,SAAS,IAAI,KACnC,CAAC,gBAAgB,KAAK,SAAS,IAAI;EACrC,MAAM,eACJ,YAAY,UAAU,YAAY,OAClC,YAAY,KAAK,SAAS,KAC1B,CAAC,YAAY,KAAK,SAAS,IAAI,KAC/B,YAAY,KAAK,WAAW;EAC9B,IAAI,qBAAqB,cAAc;GAQrC,IAAI,YAAY,UAAU,gBAAgB,KACxC,OAAO;GAET,SAAS;GACT,IACE,SAAS,0BACR,cAAc,mBAAmB,KAAA,KAChC,cAAc,mBAAmB,0BAClC,UAAU,mBAAmB,KAAA,KAC5B,UAAU,mBAAmB,wBAE/B,OAAO;GAET;EACF;EACA,MAAM,oBACJ,gBAAgB,KAAK,WAAW,KAChC,gBAAgB,MAAM,gBAAgB,SACtC,gBAAgB,KAAK,SAAS;EAChC,MAAM,eACJ,YAAY,KAAK,WAAW,KAC5B,YAAY,MAAM,YAAY,SAC9B,YAAY,KAAK,SAAS;EAC5B,IAAI,qBAAqB,cAAc;GACrC,IAAI;GACJ,IAAI,oBAAoB,gBAAgB,KACtC,WAAW;QACN,IACL,mBAAmB,YAAY,MAAM,YAAY,WACjD,gBAAgB,OAEhB,OAAO;QAEP,WAAW;GAEb,SAAS;GACT,IACE,SAAS,YACR,cAAc,mBAAmB,KAAA,KAChC,cAAc,mBAAmB,YAClC,UAAU,mBAAmB,KAAA,KAC5B,UAAU,mBAAmB,UAE/B,OAAO;GAET;EACF;EACA,OAAO;CACT;CACA,OAAO,SAAS,KAAA;AAClB;;AAGA,SAAgB,yBACd,eACA,WAC6B;CAC7B,MAAM,eAAmC,CAAC;CAC1C,MAAM,gBAA0B,CAAC;CACjC,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,aAAa,QAAQ,KAAK;EAC1D,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,kBAAkB,cAAc,aAAa;EACnD,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,cAAc,UAAU,aAAa;EAC3C,MAAM,kBAAkB,mCACtB,YAAY,OACZ,cAAc,YAChB;EAEA,IAAI,gBAAgB,KAAK,SAAS,GAAG;GACnC,mBAAmB;GACnB,aAAa,KAAK;IAChB,OAAO,gBAAgB;IACvB,KAAK,gBAAgB;IACrB,MAAM,gBAAgB,OAAO,YAAY;GAC3C,CAAC;GACD,cAAc,KAAK,gBAAgB,IAAI;GACvC;EACF;EAEA,IAAI,oBAAoB,gBAAgB,KAAK;GAC3C,mBAAmB;GACnB,aAAa,KAAK;IAChB,OAAO,gBAAgB;IACvB,KAAK,mBAAmB,YAAY,MAAM,YAAY;IACtD,MAAM;GACR,CAAC;GACD,cAAc,KAAK,gBAAgB,OAAO,YAAY,IAAI;GAC1D;EACF;EAEA,mBAAmB;EACnB,aAAa,KAAK;GAChB,OAAO,KAAK,IAAI,gBAAgB,OAAO,eAAe;GACtD,KAAK,gBAAgB;GACrB,MAAM;EACR,CAAC;EACD,cAAc,KAAK,YAAY,OAAO,gBAAgB,IAAI;CAC5D;CAEA,OAAO;EACL;EACA,cAAc,mCACZ,cACA,aACF;EACA,eAAe,cAAc;EAC7B,cAAc,UAAU;EACxB,kBAAkB,cAAc,kBAAkB,MAAM;EACxD,iBAAiB,UAAU,iBAAiB,MAAM;EAClD,uBAAuB,cAAc,uBAAuB,MAAM;EAClE,sBAAsB,UAAU,sBAAsB,MAAM;EAC5D;CACF;AACF;AAEA,SAAS,mCACP,cACA,eACoB;CACpB,MAAM,eAAmC,CAAC;CAC1C,KAAK,IAAI,IAAI,GAAG,cAAc,GAAG,IAAI,aAAa,QAAQ,KAAK;EAC7D,MAAM,OAAO,aAAa;EAC1B,MAAM,iBAAiB,KAAK,QAAQ;EACpC,aAAa,KAAK;GAChB,OAAO;GACP,KAAK,iBAAiB,KAAK,KAAK;GAChC,MAAM,cAAc;EACtB,CAAC;EACD,eAAe,KAAK,KAAK,UAAU,KAAK,MAAM,KAAK;CACrD;CACA,OAAO;AACT;AAEA,SAAS,mCACP,aACA,cACQ;CACR,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,YAAY,KAAK,MAAM,KAAK;EAClC,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,QAAQ,YAAY;EAC1B,IAAI,SAAS,KAAK,OAChB;EAEF,IAAI,UAAU,KAAK,QAAQ,WAAW;GACpC,UAAU;GACV;EACF;EACA,SAAS,KAAK,QAAQ,KAAK,IAAI,SAAS,KAAK,OAAO,SAAS;CAC/D;CACA,OAAO;AACT"}
|
|
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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"}
|
package/dist/editor/editor.d.ts
CHANGED
|
@@ -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
|
|
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":"
|
|
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"}
|
package/dist/editor/editor.js
CHANGED
|
@@ -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 {
|
|
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.#
|
|
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.#
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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.#
|
|
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)
|
|
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
|
-
},
|
|
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 =
|
|
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;
|