@sunub/obsidian-mcp-server 0.3.23 → 0.3.24

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 (135) hide show
  1. package/build/cli/App.d.ts +4 -1
  2. package/build/cli/App.js +158 -104
  3. package/build/cli/App.js.map +1 -1
  4. package/build/cli/AppContainer.js +8 -2
  5. package/build/cli/AppContainer.js.map +1 -1
  6. package/build/cli/config/mcpServersConfig.js +19 -2
  7. package/build/cli/config/mcpServersConfig.js.map +1 -1
  8. package/build/cli/context/InputContext.d.ts +1 -1
  9. package/build/cli/context/KeypressContext.js +0 -5
  10. package/build/cli/context/KeypressContext.js.map +1 -1
  11. package/build/cli/context/KeypressContext.util.js +1 -2
  12. package/build/cli/context/KeypressContext.util.js.map +1 -1
  13. package/build/cli/context/UIStateContext.d.ts +16 -0
  14. package/build/cli/context/UIStateContext.js +10 -0
  15. package/build/cli/context/UIStateContext.js.map +1 -0
  16. package/build/cli/hooks/useCommandCompletion.d.ts +1 -1
  17. package/build/cli/hooks/useCommandCompletion.js +1 -1
  18. package/build/cli/hooks/useCommandCompletion.js.map +1 -1
  19. package/build/cli/hooks/useHistoryManager.d.ts +1 -0
  20. package/build/cli/hooks/useHistoryManager.js +19 -1
  21. package/build/cli/hooks/useHistoryManager.js.map +1 -1
  22. package/build/cli/hooks/useInputHistory.d.ts +2 -2
  23. package/build/cli/hooks/useInputHistory.js +2 -2
  24. package/build/cli/hooks/useInputHistory.js.map +1 -1
  25. package/build/cli/hooks/useKeyMatchers.d.ts +0 -9
  26. package/build/cli/hooks/useKeyMatchers.js +0 -9
  27. package/build/cli/hooks/useKeyMatchers.js.map +1 -1
  28. package/build/cli/hooks/useKeypress.d.ts +0 -13
  29. package/build/cli/hooks/useKeypress.js +0 -13
  30. package/build/cli/hooks/useKeypress.js.map +1 -1
  31. package/build/cli/hooks/useLlmStream/useLlmStream.d.ts +1 -1
  32. package/build/cli/hooks/useLlmStream/useLlmStream.js +9 -3
  33. package/build/cli/hooks/useLlmStream/useLlmStream.js.map +1 -1
  34. package/build/cli/hooks/useMcpManager.d.ts +0 -10
  35. package/build/cli/hooks/useMcpManager.js +6 -0
  36. package/build/cli/hooks/useMcpManager.js.map +1 -1
  37. package/build/cli/hooks/usePromptCompletion.d.ts +1 -2
  38. package/build/cli/hooks/usePromptCompletion.js +0 -1
  39. package/build/cli/hooks/usePromptCompletion.js.map +1 -1
  40. package/build/cli/hooks/useTransientMessage.d.ts +7 -0
  41. package/build/cli/hooks/useTransientMessage.js +34 -0
  42. package/build/cli/hooks/useTransientMessage.js.map +1 -0
  43. package/build/cli/index.js +7 -3
  44. package/build/cli/index.js.map +1 -1
  45. package/build/cli/key/keyBindings.d.ts +0 -19
  46. package/build/cli/key/keyBindings.js +0 -14
  47. package/build/cli/key/keyBindings.js.map +1 -1
  48. package/build/cli/key/keyMatchers.d.ts +0 -9
  49. package/build/cli/key/keyMatchers.js +0 -8
  50. package/build/cli/key/keyMatchers.js.map +1 -1
  51. package/build/cli/key/keybindingUtils.d.ts +3 -0
  52. package/build/cli/key/keybindingUtils.js +74 -0
  53. package/build/cli/key/keybindingUtils.js.map +1 -0
  54. package/build/cli/key/textBuffer/constants.d.ts +1 -0
  55. package/build/cli/key/textBuffer/constants.js +2 -0
  56. package/build/cli/key/textBuffer/constants.js.map +1 -0
  57. package/build/cli/key/textBuffer/index.d.ts +3 -0
  58. package/build/cli/key/textBuffer/index.js +3 -0
  59. package/build/cli/key/textBuffer/index.js.map +1 -0
  60. package/build/cli/key/textBuffer/textBuffer.d.ts +73 -0
  61. package/build/cli/key/textBuffer/textBuffer.js +1154 -0
  62. package/build/cli/key/textBuffer/textBuffer.js.map +1 -0
  63. package/build/cli/key/textBuffer/types.d.ts +106 -0
  64. package/build/cli/key/textBuffer/types.js +2 -0
  65. package/build/cli/key/textBuffer/types.js.map +1 -0
  66. package/build/cli/services/InputOffloadService.d.ts +20 -0
  67. package/build/cli/services/InputOffloadService.js +105 -0
  68. package/build/cli/services/InputOffloadService.js.map +1 -0
  69. package/build/cli/services/McpClientService.js +5 -3
  70. package/build/cli/services/McpClientService.js.map +1 -1
  71. package/build/cli/services/McpManager.js +4 -2
  72. package/build/cli/services/McpManager.js.map +1 -1
  73. package/build/cli/theme/builtin/tokyonight-dark.js +1 -0
  74. package/build/cli/theme/builtin/tokyonight-dark.js.map +1 -1
  75. package/build/cli/theme/semantic-tokens.js +8 -8
  76. package/build/cli/theme/semantic-tokens.js.map +1 -1
  77. package/build/cli/theme/theme-manager.js +4 -2
  78. package/build/cli/theme/theme-manager.js.map +1 -1
  79. package/build/cli/theme/theme.d.ts +1 -23
  80. package/build/cli/theme/theme.js +11 -31
  81. package/build/cli/theme/theme.js.map +1 -1
  82. package/build/cli/types.d.ts +1 -2
  83. package/build/cli/ui/CLILogo.d.ts +1 -0
  84. package/build/cli/ui/CLILogo.js +33 -0
  85. package/build/cli/ui/CLILogo.js.map +1 -0
  86. package/build/cli/ui/HistoryItemDisplay.js +36 -1
  87. package/build/cli/ui/HistoryItemDisplay.js.map +1 -1
  88. package/build/cli/ui/InputPrompt.d.ts +8 -1
  89. package/build/cli/ui/InputPrompt.js +142 -24
  90. package/build/cli/ui/InputPrompt.js.map +1 -1
  91. package/build/cli/ui/LLMHealthChecker.js +1 -1
  92. package/build/cli/ui/LLMStatusLoader.d.ts +10 -0
  93. package/build/cli/ui/LLMStatusLoader.js +35 -0
  94. package/build/cli/ui/LLMStatusLoader.js.map +1 -0
  95. package/build/cli/ui/MainContent.js +2 -7
  96. package/build/cli/ui/MainContent.js.map +1 -1
  97. package/build/cli/ui/SystemInfoSummaryBox.js +3 -2
  98. package/build/cli/ui/SystemInfoSummaryBox.js.map +1 -1
  99. package/build/cli/ui/ThinkingIndicator.d.ts +3 -2
  100. package/build/cli/ui/ThinkingIndicator.js +3 -3
  101. package/build/cli/ui/ThinkingIndicator.js.map +1 -1
  102. package/build/cli/ui/ToastDisplay.d.ts +2 -0
  103. package/build/cli/ui/ToastDisplay.js +19 -0
  104. package/build/cli/ui/ToastDisplay.js.map +1 -0
  105. package/build/cli/ui/shared/ExpandableText.d.ts +0 -3
  106. package/build/cli/ui/shared/ExpandableText.js +0 -3
  107. package/build/cli/ui/shared/ExpandableText.js.map +1 -1
  108. package/build/cli/utils/bootLogo.d.ts +8 -0
  109. package/build/cli/utils/bootLogo.js +68 -0
  110. package/build/cli/utils/bootLogo.js.map +1 -0
  111. package/build/cli/utils/events.d.ts +28 -0
  112. package/build/cli/utils/events.js +18 -0
  113. package/build/cli/utils/events.js.map +1 -0
  114. package/build/cli/utils/highlights.d.ts +8 -0
  115. package/build/cli/utils/highlights.js +117 -0
  116. package/build/cli/utils/highlights.js.map +1 -0
  117. package/build/cli/utils/textUtil.d.ts +5 -0
  118. package/build/cli/utils/textUtil.js +32 -0
  119. package/build/cli/utils/textUtil.js.map +1 -1
  120. package/build/shared/utils/debugLogger.d.ts +4 -0
  121. package/build/shared/utils/debugLogger.js +12 -0
  122. package/build/shared/utils/debugLogger.js.map +1 -1
  123. package/build/utils/cleanup.d.ts +30 -0
  124. package/build/utils/cleanup.js +137 -0
  125. package/build/utils/cleanup.js.map +1 -0
  126. package/package.json +4 -2
  127. package/build/cli/key/text-buffer.d.ts +0 -156
  128. package/build/cli/key/text-buffer.js +0 -937
  129. package/build/cli/key/text-buffer.js.map +0 -1
  130. package/build/cli/tmp.d.ts +0 -1
  131. package/build/cli/tmp.js +0 -27
  132. package/build/cli/tmp.js.map +0 -1
  133. package/build/cli/ui/layout/DefaultAppLayout.d.ts +0 -2
  134. package/build/cli/ui/layout/DefaultAppLayout.js +0 -6
  135. package/build/cli/ui/layout/DefaultAppLayout.js.map +0 -1
@@ -1,937 +0,0 @@
1
- import { LRU_BUFFER_PERF_CACHE_LIMIT } from "../constants.js";
2
- import { cpLen, cpSlice, getCachedStringWidth, toCodePoints, } from "../utils/textUtil.js";
3
- import { LRUCache } from "mnemonist";
4
- import { useCallback, useEffect, useMemo, useReducer } from "react";
5
- export const PASTED_TEXT_PLACEHOLDER_REGEX = /\[Pasted Text: \d+ (?:lines|chars)(?: #\d+)?\]/g;
6
- const lineLayoutCache = new LRUCache(LRU_BUFFER_PERF_CACHE_LIMIT);
7
- function getLineLayoutCacheKey(line, viewportWidth, isCursorOnLine, cursorCol) {
8
- // Most lines (99.9% in a large buffer) are not cursor lines.
9
- // We use a simpler key for them to reduce string allocation overhead.
10
- if (!isCursorOnLine) {
11
- return `${viewportWidth}:N:${line}`;
12
- }
13
- return `${viewportWidth}:C:${cursorCol}:${line}`;
14
- }
15
- function calculateVisualCursorFromLayout(layout, logicalCursor) {
16
- const { logicalToVisualMap, visualLines, transformedToLogicalMaps } = layout;
17
- const [logicalRow, logicalCol] = logicalCursor;
18
- const segmentsForLogicalLine = logicalToVisualMap[logicalRow];
19
- if (!segmentsForLogicalLine || segmentsForLogicalLine.length === 0) {
20
- // This can happen for an empty document.
21
- return [0, 0, 0];
22
- }
23
- // Find the segment where the logical column fits.
24
- // The segments are sorted by startColInLogical.
25
- let targetSegmentIndex = segmentsForLogicalLine.findIndex(([, startColInLogical], index) => {
26
- const nextStartColInLogical = index + 1 < segmentsForLogicalLine.length
27
- ? segmentsForLogicalLine[index + 1][1]
28
- : Infinity;
29
- return (logicalCol >= startColInLogical && logicalCol < nextStartColInLogical);
30
- });
31
- // If not found, it means the cursor is at the end of the logical line.
32
- if (targetSegmentIndex === -1) {
33
- if (logicalCol === 0) {
34
- targetSegmentIndex = 0;
35
- }
36
- else {
37
- targetSegmentIndex = segmentsForLogicalLine.length - 1;
38
- }
39
- }
40
- const [visualRow, startColInLogical] = segmentsForLogicalLine[targetSegmentIndex];
41
- // Find the coordinates in transformed space in order to conver to visual
42
- const transformedToLogicalMap = transformedToLogicalMaps[logicalRow] ?? [];
43
- let transformedCol = 0;
44
- for (let i = 0; i < transformedToLogicalMap.length; i++) {
45
- if (transformedToLogicalMap[i] > logicalCol) {
46
- transformedCol = Math.max(0, i - 1);
47
- break;
48
- }
49
- if (i === transformedToLogicalMap.length - 1) {
50
- transformedCol = transformedToLogicalMap.length - 1;
51
- }
52
- }
53
- let startColInTransformed = 0;
54
- while (startColInTransformed < transformedToLogicalMap.length &&
55
- transformedToLogicalMap[startColInTransformed] < startColInLogical) {
56
- startColInTransformed++;
57
- }
58
- const clampedTransformedCol = Math.min(transformedCol, Math.max(0, transformedToLogicalMap.length - 1));
59
- const visualColIndex = clampedTransformedCol - startColInTransformed;
60
- const currentVisualLineText = visualLines[visualRow] ?? "";
61
- let visualColWidth = 0;
62
- const codePoints = toCodePoints(currentVisualLineText);
63
- for (let i = 0; i < Math.min(visualColIndex, codePoints.length); i++) {
64
- visualColWidth += getCachedStringWidth(codePoints[i]);
65
- }
66
- const clampedVisualColIndex = Math.min(Math.max(visualColIndex, 0), codePoints.length);
67
- return [visualRow, clampedVisualColIndex, visualColWidth];
68
- }
69
- /**
70
- * Helper: Converts logical row/col position to absolute text offset
71
- */
72
- export function logicalPosToOffset(lines, row, col) {
73
- let offset = 0;
74
- const actualRow = Math.min(row, lines.length - 1);
75
- for (let i = 0; i < actualRow; i++) {
76
- offset += cpLen(lines[i]) + 1; // +1 for newline
77
- }
78
- if (actualRow >= 0 && actualRow < lines.length) {
79
- offset += Math.min(col, cpLen(lines[actualRow]));
80
- }
81
- return offset;
82
- }
83
- /**
84
- * Helper: Converts absolute text offset to logical row/col position
85
- */
86
- export function offsetToLogicalPos(text, offset) {
87
- const lines = text.split("\n");
88
- let currentOffset = 0;
89
- for (let i = 0; i < lines.length; i++) {
90
- const lineLen = cpLen(lines[i]);
91
- const lineLenWithNewline = lineLen + (i < lines.length - 1 ? 1 : 0);
92
- if (offset <= currentOffset + lineLen) {
93
- return [i, offset - currentOffset];
94
- }
95
- if (offset < currentOffset + lineLenWithNewline) {
96
- return [i, lineLen];
97
- }
98
- currentOffset += lineLenWithNewline;
99
- }
100
- const lastRow = Math.max(0, lines.length - 1);
101
- return [lastRow, cpLen(lines[lastRow] || "")];
102
- }
103
- function bufferReducerLogic(state, action) {
104
- const { lines, cursorRow, cursorCol } = state;
105
- const currentLineText = lines[cursorRow] || "";
106
- const lineCount = lines.length;
107
- const pushUndo = (_newLines) => {
108
- return [...state.undoStack, lines].slice(-100);
109
- };
110
- switch (action.type) {
111
- case "SET_VIEWPORT":
112
- return {
113
- ...state,
114
- viewportWidth: action.payload.width,
115
- viewportHeight: action.payload.height,
116
- };
117
- case "SET_TEXT": {
118
- const newLines = action.payload.replace(/\r\n/g, "\n").split("\n");
119
- const safeLines = newLines.length > 0 ? newLines : [""];
120
- let r = 0;
121
- let c = 0;
122
- if (action.cursorPosition === "end") {
123
- r = safeLines.length - 1;
124
- c = cpLen(safeLines[r]);
125
- }
126
- else if (typeof action.cursorPosition === "number") {
127
- [r, c] = offsetToLogicalPos(action.payload, action.cursorPosition);
128
- }
129
- return {
130
- ...state,
131
- lines: safeLines,
132
- cursorRow: r,
133
- cursorCol: c,
134
- preferredCol: null,
135
- undoStack: pushUndo(safeLines),
136
- redoStack: [],
137
- };
138
- }
139
- case "INSERT": {
140
- const payload = action.payload
141
- .replace(/\r\n/g, "\n")
142
- .replace(/\r/g, "\n");
143
- const parts = payload.split("\n");
144
- const newLines = [...lines];
145
- const before = cpSlice(currentLineText, 0, cursorCol);
146
- const after = cpSlice(currentLineText, cursorCol);
147
- let nextRow = cursorRow;
148
- let nextCol = cursorCol;
149
- if (parts.length === 1) {
150
- newLines[cursorRow] = before + parts[0] + after;
151
- nextCol = cursorCol + cpLen(parts[0]);
152
- }
153
- else {
154
- const firstLine = before + parts[0];
155
- const lastPart = parts[parts.length - 1];
156
- const lastLine = lastPart + after;
157
- const middle = parts.slice(1, -1);
158
- newLines.splice(cursorRow, 1, firstLine, ...middle, lastLine);
159
- nextRow = cursorRow + parts.length - 1;
160
- nextCol = cpLen(lastPart);
161
- }
162
- return {
163
- ...state,
164
- lines: newLines,
165
- cursorRow: nextRow,
166
- cursorCol: nextCol,
167
- preferredCol: null,
168
- undoStack: pushUndo(newLines),
169
- redoStack: [],
170
- };
171
- }
172
- case "NEWLINE": {
173
- const newLines = [...lines];
174
- const before = cpSlice(currentLineText, 0, cursorCol);
175
- const after = cpSlice(currentLineText, cursorCol);
176
- newLines[cursorRow] = before;
177
- newLines.splice(cursorRow + 1, 0, after);
178
- return {
179
- ...state,
180
- lines: newLines,
181
- cursorRow: cursorRow + 1,
182
- cursorCol: 0,
183
- preferredCol: null,
184
- undoStack: pushUndo(newLines),
185
- redoStack: [],
186
- };
187
- }
188
- case "BACKSPACE": {
189
- if (cursorCol === 0 && cursorRow === 0)
190
- return state;
191
- const newLines = [...lines];
192
- if (cursorCol > 0) {
193
- newLines[cursorRow] =
194
- cpSlice(currentLineText, 0, cursorCol - 1) +
195
- cpSlice(currentLineText, cursorCol);
196
- return {
197
- ...state,
198
- lines: newLines,
199
- cursorCol: cursorCol - 1,
200
- preferredCol: null,
201
- undoStack: pushUndo(newLines),
202
- redoStack: [],
203
- };
204
- }
205
- else {
206
- const prevLine = lines[cursorRow - 1];
207
- const prevLen = cpLen(prevLine);
208
- newLines[cursorRow - 1] = prevLine + currentLineText;
209
- newLines.splice(cursorRow, 1);
210
- return {
211
- ...state,
212
- lines: newLines,
213
- cursorRow: cursorRow - 1,
214
- cursorCol: prevLen,
215
- preferredCol: null,
216
- undoStack: pushUndo(newLines),
217
- redoStack: [],
218
- };
219
- }
220
- }
221
- case "DELETE": {
222
- if (cursorCol === cpLen(currentLineText) && cursorRow === lineCount - 1)
223
- return state;
224
- const newLines = [...lines];
225
- if (cursorCol < cpLen(currentLineText)) {
226
- newLines[cursorRow] =
227
- cpSlice(currentLineText, 0, cursorCol) +
228
- cpSlice(currentLineText, cursorCol + 1);
229
- }
230
- else {
231
- newLines[cursorRow] = currentLineText + lines[cursorRow + 1];
232
- newLines.splice(cursorRow + 1, 1);
233
- }
234
- return {
235
- ...state,
236
- lines: newLines,
237
- preferredCol: null,
238
- undoStack: pushUndo(newLines),
239
- redoStack: [],
240
- };
241
- }
242
- case "KILL_LINE_RIGHT": {
243
- const newLines = [...lines];
244
- newLines[cursorRow] = cpSlice(currentLineText, 0, cursorCol);
245
- return {
246
- ...state,
247
- lines: newLines,
248
- preferredCol: null,
249
- undoStack: pushUndo(newLines),
250
- redoStack: [],
251
- };
252
- }
253
- case "KILL_LINE_LEFT": {
254
- const newLines = [...lines];
255
- newLines[cursorRow] = cpSlice(currentLineText, cursorCol);
256
- return {
257
- ...state,
258
- lines: newLines,
259
- cursorCol: 0,
260
- preferredCol: null,
261
- undoStack: pushUndo(newLines),
262
- redoStack: [],
263
- };
264
- }
265
- case "DELETE_WORD_LEFT": {
266
- if (cursorCol === 0)
267
- return state;
268
- const before = cpSlice(currentLineText, 0, cursorCol);
269
- const after = cpSlice(currentLineText, cursorCol);
270
- // Simple word boundary: skip whitespace then skip non-whitespace
271
- let i = before.length - 1;
272
- while (i >= 0 && before[i] === " ")
273
- i--;
274
- while (i >= 0 && before[i] !== " ")
275
- i--;
276
- const newBefore = before.slice(0, i + 1);
277
- const newLines = [...lines];
278
- newLines[cursorRow] = newBefore + after;
279
- return {
280
- ...state,
281
- lines: newLines,
282
- cursorCol: cpLen(newBefore),
283
- preferredCol: null,
284
- undoStack: pushUndo(newLines),
285
- redoStack: [],
286
- };
287
- }
288
- case "DELETE_WORD_RIGHT": {
289
- const before = cpSlice(currentLineText, 0, cursorCol);
290
- const after = cpSlice(currentLineText, cursorCol);
291
- if (after.length === 0)
292
- return state;
293
- let i = 0;
294
- while (i < after.length && after[i] === " ")
295
- i++;
296
- while (i < after.length && after[i] !== " ")
297
- i++;
298
- const newAfter = after.slice(i);
299
- const newLines = [...lines];
300
- newLines[cursorRow] = before + newAfter;
301
- return {
302
- ...state,
303
- lines: newLines,
304
- preferredCol: null,
305
- undoStack: pushUndo(newLines),
306
- redoStack: [],
307
- };
308
- }
309
- case "MOVE": {
310
- const { dir } = action;
311
- const { visualLayout, preferredCol } = state;
312
- const [vRow, , vWidth] = calculateVisualCursorFromLayout(visualLayout, [
313
- cursorRow,
314
- cursorCol,
315
- ]);
316
- if (dir === "left" ||
317
- dir === "right" ||
318
- dir === "home" ||
319
- dir === "end") {
320
- let r = cursorRow;
321
- let c = cursorCol;
322
- if (dir === "left") {
323
- if (c > 0)
324
- c--;
325
- else if (r > 0) {
326
- r--;
327
- c = cpLen(lines[r]);
328
- }
329
- }
330
- else if (dir === "right") {
331
- if (c < cpLen(currentLineText))
332
- c++;
333
- else if (r < lineCount - 1) {
334
- r++;
335
- c = 0;
336
- }
337
- }
338
- else if (dir === "home")
339
- c = 0;
340
- else if (dir === "end")
341
- c = cpLen(currentLineText);
342
- return { ...state, cursorRow: r, cursorCol: c, preferredCol: null };
343
- }
344
- // Up/Down movement (width-based)
345
- let newVisRow = vRow;
346
- const targetWidth = preferredCol !== null ? preferredCol : vWidth;
347
- if (dir === "up" && newVisRow > 0) {
348
- newVisRow--;
349
- }
350
- else if (dir === "down" &&
351
- newVisRow < visualLayout.visualLines.length - 1) {
352
- newVisRow++;
353
- }
354
- else {
355
- return state;
356
- }
357
- // Find the logical index in the new visual line that best matches the targetWidth
358
- const newLineText = visualLayout.visualLines[newVisRow] ?? "";
359
- const codePoints = toCodePoints(newLineText);
360
- let currentWidth = 0;
361
- let newVisColIdx = 0;
362
- for (let i = 0; i < codePoints.length; i++) {
363
- const charWidth = getCachedStringWidth(codePoints[i]);
364
- if (currentWidth + charWidth > targetWidth) {
365
- break;
366
- }
367
- currentWidth += charWidth;
368
- newVisColIdx = i + 1;
369
- }
370
- const mapping = visualLayout.visualToLogicalMap[newVisRow];
371
- if (mapping) {
372
- return {
373
- ...state,
374
- cursorRow: mapping[0],
375
- cursorCol: mapping[1] + newVisColIdx,
376
- preferredCol: targetWidth,
377
- };
378
- }
379
- return state;
380
- }
381
- case "REPLACE_RANGE": {
382
- const { startRow, startCol, endRow, endCol, text } = action.payload;
383
- const newLines = [...lines];
384
- const prefix = cpSlice(lines[startRow] || "", 0, startCol);
385
- const suffix = cpSlice(lines[endRow] || "", endCol);
386
- const replacementParts = text.replace(/\r\n/g, "\n").split("\n");
387
- const firstPart = prefix + replacementParts[0];
388
- const lastPart = replacementParts[replacementParts.length - 1] + suffix;
389
- if (replacementParts.length === 1) {
390
- newLines.splice(startRow, endRow - startRow + 1, firstPart + suffix);
391
- }
392
- else {
393
- const middleParts = replacementParts.slice(1, -1);
394
- newLines.splice(startRow, endRow - startRow + 1, firstPart, ...middleParts, lastPart);
395
- }
396
- return {
397
- ...state,
398
- lines: newLines,
399
- cursorRow: startRow + replacementParts.length - 1,
400
- cursorCol: cpLen(replacementParts[replacementParts.length - 1]),
401
- preferredCol: null,
402
- undoStack: pushUndo(newLines),
403
- redoStack: [],
404
- };
405
- }
406
- case "UNDO": {
407
- if (state.undoStack.length === 0)
408
- return state;
409
- const prevLines = state.undoStack[state.undoStack.length - 1];
410
- return {
411
- ...state,
412
- lines: prevLines,
413
- undoStack: state.undoStack.slice(0, -1),
414
- redoStack: [...state.redoStack, lines],
415
- cursorRow: Math.max(0, prevLines.length - 1),
416
- cursorCol: cpLen(prevLines[prevLines.length - 1] || ""),
417
- preferredCol: null,
418
- };
419
- }
420
- case "REDO": {
421
- if (state.redoStack.length === 0)
422
- return state;
423
- const nextLines = state.redoStack[state.redoStack.length - 1];
424
- return {
425
- ...state,
426
- lines: nextLines,
427
- redoStack: state.redoStack.slice(0, -1),
428
- undoStack: [...state.undoStack, lines],
429
- cursorRow: Math.max(0, nextLines.length - 1),
430
- cursorCol: cpLen(nextLines[nextLines.length - 1] || ""),
431
- preferredCol: null,
432
- };
433
- }
434
- default:
435
- return state;
436
- }
437
- }
438
- const transformationsCache = new LRUCache(LRU_BUFFER_PERF_CACHE_LIMIT);
439
- export function calculateTransformationsForLine(line) {
440
- const cached = transformationsCache.get(line);
441
- if (cached) {
442
- return cached;
443
- }
444
- const transformations = [];
445
- // 2. Detect paste placeholders
446
- const pasteRegex = new RegExp(PASTED_TEXT_PLACEHOLDER_REGEX.source, "g");
447
- const match = pasteRegex.exec(line);
448
- while (match !== null) {
449
- const logicalText = match[0];
450
- const logStart = cpLen(line.substring(0, match.index));
451
- const logEnd = logStart + cpLen(logicalText);
452
- transformations.push({
453
- logStart,
454
- logEnd,
455
- logicalText,
456
- collapsedText: logicalText,
457
- type: "paste",
458
- id: logicalText,
459
- });
460
- }
461
- // Sort transformations by logStart to maintain consistency
462
- transformations.sort((a, b) => a.logStart - b.logStart);
463
- transformationsCache.set(line, transformations);
464
- return transformations;
465
- }
466
- export function calculateTransformedLine(logLine, logIndex, logicalCursor, transformations) {
467
- let transformedLine = "";
468
- const transformedToLogMap = [];
469
- let lastLogPos = 0;
470
- const cursorIsOnThisLine = logIndex === logicalCursor[0];
471
- const cursorCol = logicalCursor[1];
472
- for (const transform of transformations) {
473
- const textBeforeTransformation = cpSlice(logLine, lastLogPos, transform.logStart);
474
- transformedLine += textBeforeTransformation;
475
- for (let i = 0; i < cpLen(textBeforeTransformation); i++) {
476
- transformedToLogMap.push(lastLogPos + i);
477
- }
478
- const isExpanded = transform.type === "image" &&
479
- cursorIsOnThisLine &&
480
- cursorCol >= transform.logStart &&
481
- cursorCol <= transform.logEnd;
482
- const transformedText = isExpanded
483
- ? transform.logicalText
484
- : transform.collapsedText;
485
- transformedLine += transformedText;
486
- // Map transformed characters back to logical characters
487
- const transformedLen = cpLen(transformedText);
488
- if (isExpanded) {
489
- for (let i = 0; i < transformedLen; i++) {
490
- transformedToLogMap.push(transform.logStart + i);
491
- }
492
- }
493
- else {
494
- // Collapsed: distribute transformed positions monotonically across the raw span.
495
- // This preserves ordering across wrapped slices so logicalToVisualMap has
496
- // increasing startColInLogical and visual cursor mapping remains consistent.
497
- const logicalLength = Math.max(0, transform.logEnd - transform.logStart);
498
- for (let i = 0; i < transformedLen; i++) {
499
- // Map the i-th transformed code point into [logStart, logEnd)
500
- const transformationToLogicalOffset = logicalLength === 0
501
- ? 0
502
- : Math.floor((i * logicalLength) / transformedLen);
503
- const transformationToLogicalIndex = transform.logStart +
504
- Math.min(transformationToLogicalOffset, Math.max(logicalLength - 1, 0));
505
- transformedToLogMap.push(transformationToLogicalIndex);
506
- }
507
- }
508
- lastLogPos = transform.logEnd;
509
- }
510
- // Append text after last transform
511
- const remainingUntransformedText = cpSlice(logLine, lastLogPos);
512
- transformedLine += remainingUntransformedText;
513
- for (let i = 0; i < cpLen(remainingUntransformedText); i++) {
514
- transformedToLogMap.push(lastLogPos + i);
515
- }
516
- // For a cursor at the very end of the transformed line
517
- transformedToLogMap.push(cpLen(logLine));
518
- return { transformedLine, transformedToLogMap };
519
- }
520
- function calculateLayout(logicalLines, viewportWidth, logicalCursor) {
521
- const visualLines = [];
522
- const logicalToVisualMap = [];
523
- const visualToLogicalMap = [];
524
- const transformedToLogicalMaps = [];
525
- const visualToTransformedMap = [];
526
- logicalLines.forEach((logLine, logIndex) => {
527
- logicalToVisualMap[logIndex] = [];
528
- const isCursorOnLine = logIndex === logicalCursor[0];
529
- const cacheKey = getLineLayoutCacheKey(logLine, viewportWidth, isCursorOnLine, logicalCursor[1]);
530
- const cached = lineLayoutCache.get(cacheKey);
531
- if (cached) {
532
- const visualLineOffset = visualLines.length;
533
- visualLines.push(...cached.visualLines);
534
- cached.logicalToVisualMap.forEach(([relVisualIdx, logCol]) => {
535
- logicalToVisualMap[logIndex].push([
536
- visualLineOffset + relVisualIdx,
537
- logCol,
538
- ]);
539
- });
540
- cached.visualToLogicalMap.forEach(([, logCol]) => {
541
- visualToLogicalMap.push([logIndex, logCol]);
542
- });
543
- transformedToLogicalMaps[logIndex] = cached.transformedToLogMap;
544
- visualToTransformedMap.push(...cached.visualToTransformedMap);
545
- return;
546
- }
547
- // Not in cache, calculate
548
- const transformations = calculateTransformationsForLine(logLine);
549
- const { transformedLine, transformedToLogMap } = calculateTransformedLine(logLine, logIndex, logicalCursor, transformations);
550
- const lineVisualLines = [];
551
- const lineLogicalToVisualMap = [];
552
- const lineVisualToLogicalMap = [];
553
- const lineVisualToTransformedMap = [];
554
- if (transformedLine.length === 0) {
555
- // Handle empty logical line
556
- lineLogicalToVisualMap.push([0, 0]);
557
- lineVisualToLogicalMap.push([logIndex, 0]);
558
- lineVisualToTransformedMap.push(0);
559
- lineVisualLines.push("");
560
- }
561
- else {
562
- // Non-empty logical line
563
- let currentPosInLogLine = 0; // Tracks position within the current logical line (code point index)
564
- const codePointsInLogLine = toCodePoints(transformedLine);
565
- while (currentPosInLogLine < codePointsInLogLine.length) {
566
- let currentChunk = "";
567
- let currentChunkVisualWidth = 0;
568
- let numCodePointsInChunk = 0;
569
- let lastWordBreakPoint = -1; // Index in codePointsInLogLine for word break
570
- let numCodePointsAtLastWordBreak = 0;
571
- // Iterate through code points to build the current visual line (chunk)
572
- for (let i = currentPosInLogLine; i < codePointsInLogLine.length; i++) {
573
- const char = codePointsInLogLine[i];
574
- const charVisualWidth = getCachedStringWidth(char);
575
- if (currentChunkVisualWidth + charVisualWidth > viewportWidth) {
576
- // Character would exceed viewport width
577
- if (lastWordBreakPoint !== -1 &&
578
- numCodePointsAtLastWordBreak > 0 &&
579
- currentPosInLogLine + numCodePointsAtLastWordBreak < i) {
580
- // We have a valid word break point to use, and it's not the start of the current segment
581
- currentChunk = codePointsInLogLine
582
- .slice(currentPosInLogLine, currentPosInLogLine + numCodePointsAtLastWordBreak)
583
- .join("");
584
- numCodePointsInChunk = numCodePointsAtLastWordBreak;
585
- }
586
- else {
587
- // No word break, or word break is at the start of this potential chunk, or word break leads to empty chunk.
588
- // Hard break: take characters up to viewportWidth, or just the current char if it alone is too wide.
589
- if (numCodePointsInChunk === 0 &&
590
- charVisualWidth > viewportWidth) {
591
- // Single character is wider than viewport, take it anyway
592
- currentChunk = char;
593
- numCodePointsInChunk = 1;
594
- }
595
- }
596
- break; // Break from inner loop to finalize this chunk
597
- }
598
- currentChunk += char;
599
- currentChunkVisualWidth += charVisualWidth;
600
- numCodePointsInChunk++;
601
- // Check for word break opportunity (space)
602
- if (char === " ") {
603
- lastWordBreakPoint = i; // Store code point index of the space
604
- // Store the state *before* adding the space, if we decide to break here.
605
- numCodePointsAtLastWordBreak = numCodePointsInChunk - 1; // Chars *before* the space
606
- }
607
- }
608
- if (numCodePointsInChunk === 0 &&
609
- currentPosInLogLine < codePointsInLogLine.length) {
610
- const firstChar = codePointsInLogLine[currentPosInLogLine];
611
- currentChunk = firstChar;
612
- numCodePointsInChunk = 1;
613
- }
614
- const logicalStartCol = transformedToLogMap[currentPosInLogLine] ?? 0;
615
- lineLogicalToVisualMap.push([lineVisualLines.length, logicalStartCol]);
616
- lineVisualToLogicalMap.push([logIndex, logicalStartCol]);
617
- lineVisualToTransformedMap.push(currentPosInLogLine);
618
- lineVisualLines.push(currentChunk);
619
- const logicalStartOfThisChunk = currentPosInLogLine;
620
- currentPosInLogLine += numCodePointsInChunk;
621
- if (logicalStartOfThisChunk + numCodePointsInChunk <
622
- codePointsInLogLine.length &&
623
- currentPosInLogLine < codePointsInLogLine.length &&
624
- codePointsInLogLine[currentPosInLogLine] === " ") {
625
- currentPosInLogLine++;
626
- }
627
- }
628
- }
629
- // Cache the result for this line
630
- lineLayoutCache.set(cacheKey, {
631
- visualLines: lineVisualLines,
632
- logicalToVisualMap: lineLogicalToVisualMap,
633
- visualToLogicalMap: lineVisualToLogicalMap,
634
- transformedToLogMap,
635
- visualToTransformedMap: lineVisualToTransformedMap,
636
- });
637
- const visualLineOffset = visualLines.length;
638
- visualLines.push(...lineVisualLines);
639
- lineLogicalToVisualMap.forEach(([relVisualIdx, logCol]) => {
640
- logicalToVisualMap[logIndex].push([
641
- visualLineOffset + relVisualIdx,
642
- logCol,
643
- ]);
644
- });
645
- lineVisualToLogicalMap.forEach(([, logCol]) => {
646
- visualToLogicalMap.push([logIndex, logCol]);
647
- });
648
- transformedToLogicalMaps[logIndex] = transformedToLogMap;
649
- visualToTransformedMap.push(...lineVisualToTransformedMap);
650
- });
651
- // If the entire logical text was empty, ensure there's one empty visual line.
652
- if (logicalLines.length === 0 ||
653
- (logicalLines.length === 1 && logicalLines[0] === "")) {
654
- if (visualLines.length === 0) {
655
- visualLines.push("");
656
- if (!logicalToVisualMap[0])
657
- logicalToVisualMap[0] = [];
658
- logicalToVisualMap[0].push([0, 0]);
659
- visualToLogicalMap.push([0, 0]);
660
- visualToTransformedMap.push(0);
661
- }
662
- }
663
- return {
664
- visualLines,
665
- logicalToVisualMap,
666
- visualToLogicalMap,
667
- transformedToLogicalMaps,
668
- visualToTransformedMap,
669
- };
670
- }
671
- function textBufferReducer(state, action) {
672
- let newState = bufferReducerLogic(state, action);
673
- // Recalculate layout if lines or width changed
674
- if (newState.lines !== state.lines ||
675
- newState.viewportWidth !== state.viewportWidth) {
676
- newState = {
677
- ...newState,
678
- visualLayout: calculateLayout(newState.lines, newState.viewportWidth, [
679
- newState.cursorRow,
680
- newState.cursorCol,
681
- ]),
682
- };
683
- }
684
- // Handle scrolling (Vertical scroll-into-view)
685
- const visualCursor = calculateVisualCursorFromLayout(newState.visualLayout, [
686
- newState.cursorRow,
687
- newState.cursorCol,
688
- ]);
689
- const cursorVisRow = visualCursor[0];
690
- let newScrollRow = newState.visualScrollRow;
691
- if (cursorVisRow < newScrollRow) {
692
- newScrollRow = cursorVisRow;
693
- }
694
- else if (cursorVisRow >= newScrollRow + newState.viewportHeight) {
695
- newScrollRow = cursorVisRow - newState.viewportHeight + 1;
696
- }
697
- // Clamp scroll row
698
- const maxScroll = Math.max(0, newState.visualLayout.visualLines.length - newState.viewportHeight);
699
- newScrollRow = Math.min(Math.max(newScrollRow, 0), maxScroll);
700
- if (newScrollRow !== newState.visualScrollRow) {
701
- newState = { ...newState, visualScrollRow: newScrollRow };
702
- }
703
- return newState;
704
- }
705
- export function useTextBuffer({ initialText = "", viewportWidth = 80, viewportHeight = 10, }) {
706
- const initialState = useMemo(() => {
707
- const lines = initialText.replace(/\r\n/g, "\n").split("\n");
708
- const safeLines = lines.length === 0 ? [""] : lines;
709
- const layout = calculateLayout(safeLines, viewportWidth, [0, 0]);
710
- return {
711
- lines: safeLines,
712
- cursorRow: 0,
713
- cursorCol: 0,
714
- preferredCol: null,
715
- undoStack: [],
716
- redoStack: [],
717
- viewportWidth,
718
- viewportHeight,
719
- visualScrollRow: 0,
720
- visualLayout: layout,
721
- };
722
- }, [initialText, viewportWidth, viewportHeight]);
723
- const [state, dispatch] = useReducer(textBufferReducer, initialState);
724
- useEffect(() => {
725
- dispatch({
726
- type: "SET_VIEWPORT",
727
- payload: { width: viewportWidth, height: viewportHeight },
728
- });
729
- }, [viewportWidth, viewportHeight]);
730
- const insert = useCallback((text, opts) => dispatch({ type: "INSERT", payload: text, isPaste: opts?.paste }), []);
731
- const setText = useCallback((text, cursorPosition) => dispatch({ type: "SET_TEXT", payload: text, cursorPosition }), []);
732
- const backspace = useCallback(() => dispatch({ type: "BACKSPACE" }), []);
733
- const deleteChar = useCallback(() => dispatch({ type: "DELETE" }), []);
734
- const newline = useCallback(() => dispatch({ type: "NEWLINE" }), []);
735
- const killLineRight = useCallback(() => dispatch({ type: "KILL_LINE_RIGHT" }), []);
736
- const killLineLeft = useCallback(() => dispatch({ type: "KILL_LINE_LEFT" }), []);
737
- const deleteWordLeft = useCallback(() => dispatch({ type: "DELETE_WORD_LEFT" }), []);
738
- const deleteWordRight = useCallback(() => dispatch({ type: "DELETE_WORD_RIGHT" }), []);
739
- const move = useCallback((dir) => dispatch({ type: "MOVE", dir }), []);
740
- const undo = useCallback(() => dispatch({ type: "UNDO" }), []);
741
- const redo = useCallback(() => dispatch({ type: "REDO" }), []);
742
- const replaceRange = useCallback((startRow, startCol, endRow, endCol, text) => dispatch({
743
- type: "REPLACE_RANGE",
744
- payload: { startRow, startCol, endRow, endCol, text },
745
- }), []);
746
- const getOffset = useCallback(() => logicalPosToOffset(state.lines, state.cursorRow, state.cursorCol), [state.lines, state.cursorRow, state.cursorCol]);
747
- const handleInput = useCallback((key) => {
748
- if (key.name === "backspace") {
749
- backspace();
750
- return true;
751
- }
752
- if (key.name === "delete") {
753
- deleteChar();
754
- return true;
755
- }
756
- if (key.name === "left") {
757
- move("left");
758
- return true;
759
- }
760
- if (key.name === "right") {
761
- move("right");
762
- return true;
763
- }
764
- if (key.name === "up") {
765
- move("up");
766
- return true;
767
- }
768
- if (key.name === "down") {
769
- move("down");
770
- return true;
771
- }
772
- if (key.name === "home") {
773
- move("home");
774
- return true;
775
- }
776
- if (key.name === "end") {
777
- move("end");
778
- return true;
779
- }
780
- if (key.name === "enter" && key.shift) {
781
- newline();
782
- return true;
783
- }
784
- // Handle character input (only insertable printable characters)
785
- if (key.insertable && key.sequence) {
786
- insert(key.sequence);
787
- return true;
788
- }
789
- return false;
790
- }, [backspace, deleteChar, move, insert, newline]);
791
- const [vRow, vColIdx, vWidth] = useMemo(() => calculateVisualCursorFromLayout(state.visualLayout, [
792
- state.cursorRow,
793
- state.cursorCol,
794
- ]), [state.visualLayout, state.cursorRow, state.cursorCol]);
795
- const visualCursor = useMemo(() => [vRow, vWidth], [vRow, vWidth]);
796
- const visualCursorColIndex = vColIdx;
797
- const viewportVisualLines = useMemo(() => state.visualLayout.visualLines.slice(state.visualScrollRow, state.visualScrollRow + state.viewportHeight), [
798
- state.visualLayout.visualLines,
799
- state.visualScrollRow,
800
- state.viewportHeight,
801
- ]);
802
- const noop = useCallback(() => { }, []);
803
- const noopCount = useCallback((_count) => { }, []);
804
- const noopAsync = useCallback(async () => { }, []);
805
- return useMemo(() => ({
806
- lines: state.lines,
807
- text: state.lines.join("\n"),
808
- cursor: [state.cursorRow, state.cursorCol],
809
- preferredCol: state.preferredCol,
810
- selectionAnchor: null,
811
- pastedContent: {},
812
- // Visual / Viewport properties
813
- allVisualLines: state.visualLayout.visualLines,
814
- viewportVisualLines,
815
- visualCursor,
816
- visualCursorColIndex,
817
- visualCursorColWidth: vWidth,
818
- visualScrollRow: state.visualScrollRow,
819
- viewportHeight: state.viewportHeight,
820
- visualToLogicalMap: state.visualLayout.visualToLogicalMap,
821
- transformedToLogicalMaps: state.visualLayout.transformedToLogicalMaps,
822
- visualToTransformedMap: state.visualLayout.visualToTransformedMap,
823
- visualLayout: state.visualLayout,
824
- // Core actions
825
- insert,
826
- setText,
827
- newline,
828
- backspace,
829
- del: deleteChar,
830
- move,
831
- undo,
832
- redo,
833
- replaceRange,
834
- deleteWordLeft,
835
- deleteWordRight,
836
- killLineRight,
837
- killLineLeft,
838
- handleInput,
839
- getOffset,
840
- // Advanced actions (stubs — not yet implemented in this buffer)
841
- openInExternalEditor: noopAsync,
842
- replaceRangeByOffset: (_s, _e, _t) => { },
843
- moveToOffset: (_offset) => { },
844
- moveToVisualPosition: (_vr, _vc) => { },
845
- getLogicalPositionFromVisual: (_vr, _vc) => null,
846
- getExpandedPasteAtLine: (_lineIndex) => null,
847
- togglePasteExpansion: (_id, _row, _col) => { },
848
- // Vim stubs
849
- vimDeleteWordForward: noopCount,
850
- vimDeleteWordBackward: noopCount,
851
- vimDeleteWordEnd: noopCount,
852
- vimDeleteBigWordForward: noopCount,
853
- vimDeleteBigWordBackward: noopCount,
854
- vimDeleteBigWordEnd: noopCount,
855
- vimChangeWordForward: noopCount,
856
- vimChangeWordBackward: noopCount,
857
- vimChangeWordEnd: noopCount,
858
- vimChangeBigWordForward: noopCount,
859
- vimChangeBigWordBackward: noopCount,
860
- vimChangeBigWordEnd: noopCount,
861
- vimDeleteLine: noopCount,
862
- vimChangeLine: noopCount,
863
- vimDeleteToEndOfLine: (_count) => { },
864
- vimDeleteToStartOfLine: noop,
865
- vimChangeToEndOfLine: (_count) => { },
866
- vimDeleteToFirstNonWhitespace: noop,
867
- vimChangeToStartOfLine: noop,
868
- vimChangeToFirstNonWhitespace: noop,
869
- vimDeleteToFirstLine: noopCount,
870
- vimDeleteToLastLine: noopCount,
871
- vimChangeMovement: (_m, _count) => { },
872
- vimMoveLeft: noopCount,
873
- vimMoveRight: noopCount,
874
- vimMoveUp: noopCount,
875
- vimMoveDown: noopCount,
876
- vimMoveWordForward: noopCount,
877
- vimMoveWordBackward: noopCount,
878
- vimMoveWordEnd: noopCount,
879
- vimMoveBigWordForward: noopCount,
880
- vimMoveBigWordBackward: noopCount,
881
- vimMoveBigWordEnd: noopCount,
882
- vimDeleteChar: noopCount,
883
- vimDeleteCharBefore: noopCount,
884
- vimToggleCase: noopCount,
885
- vimReplaceChar: (_char, _count) => { },
886
- vimFindCharForward: (_char, _count, _till) => { },
887
- vimFindCharBackward: (_char, _count, _till) => { },
888
- vimDeleteToCharForward: (_char, _count, _till) => { },
889
- vimDeleteToCharBackward: (_char, _count, _till) => { },
890
- vimInsertAtCursor: noop,
891
- vimAppendAtCursor: noop,
892
- vimOpenLineBelow: noop,
893
- vimOpenLineAbove: noop,
894
- vimAppendAtLineEnd: noop,
895
- vimInsertAtLineStart: noop,
896
- vimMoveToLineStart: noop,
897
- vimMoveToLineEnd: noop,
898
- vimMoveToFirstNonWhitespace: noop,
899
- vimMoveToFirstLine: noop,
900
- vimMoveToLastLine: noop,
901
- vimMoveToLine: noopCount,
902
- vimEscapeInsertMode: noop,
903
- vimYankLine: noopCount,
904
- vimYankWordForward: noopCount,
905
- vimYankBigWordForward: noopCount,
906
- vimYankWordEnd: noopCount,
907
- vimYankBigWordEnd: noopCount,
908
- vimYankToEndOfLine: noopCount,
909
- vimPasteAfter: noopCount,
910
- vimPasteBefore: noopCount,
911
- }), [
912
- state,
913
- viewportVisualLines,
914
- visualCursor,
915
- visualCursorColIndex,
916
- vWidth,
917
- insert,
918
- setText,
919
- newline,
920
- backspace,
921
- deleteChar,
922
- move,
923
- undo,
924
- redo,
925
- replaceRange,
926
- deleteWordLeft,
927
- deleteWordRight,
928
- killLineRight,
929
- killLineLeft,
930
- handleInput,
931
- getOffset,
932
- noop,
933
- noopCount,
934
- noopAsync,
935
- ]);
936
- }
937
- //# sourceMappingURL=text-buffer.js.map