@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.
- package/build/cli/App.d.ts +4 -1
- package/build/cli/App.js +158 -104
- package/build/cli/App.js.map +1 -1
- package/build/cli/AppContainer.js +8 -2
- package/build/cli/AppContainer.js.map +1 -1
- package/build/cli/config/mcpServersConfig.js +19 -2
- package/build/cli/config/mcpServersConfig.js.map +1 -1
- package/build/cli/context/InputContext.d.ts +1 -1
- package/build/cli/context/KeypressContext.js +0 -5
- package/build/cli/context/KeypressContext.js.map +1 -1
- package/build/cli/context/KeypressContext.util.js +1 -2
- package/build/cli/context/KeypressContext.util.js.map +1 -1
- package/build/cli/context/UIStateContext.d.ts +16 -0
- package/build/cli/context/UIStateContext.js +10 -0
- package/build/cli/context/UIStateContext.js.map +1 -0
- package/build/cli/hooks/useCommandCompletion.d.ts +1 -1
- package/build/cli/hooks/useCommandCompletion.js +1 -1
- package/build/cli/hooks/useCommandCompletion.js.map +1 -1
- package/build/cli/hooks/useHistoryManager.d.ts +1 -0
- package/build/cli/hooks/useHistoryManager.js +19 -1
- package/build/cli/hooks/useHistoryManager.js.map +1 -1
- package/build/cli/hooks/useInputHistory.d.ts +2 -2
- package/build/cli/hooks/useInputHistory.js +2 -2
- package/build/cli/hooks/useInputHistory.js.map +1 -1
- package/build/cli/hooks/useKeyMatchers.d.ts +0 -9
- package/build/cli/hooks/useKeyMatchers.js +0 -9
- package/build/cli/hooks/useKeyMatchers.js.map +1 -1
- package/build/cli/hooks/useKeypress.d.ts +0 -13
- package/build/cli/hooks/useKeypress.js +0 -13
- package/build/cli/hooks/useKeypress.js.map +1 -1
- package/build/cli/hooks/useLlmStream/useLlmStream.d.ts +1 -1
- package/build/cli/hooks/useLlmStream/useLlmStream.js +9 -3
- package/build/cli/hooks/useLlmStream/useLlmStream.js.map +1 -1
- package/build/cli/hooks/useMcpManager.d.ts +0 -10
- package/build/cli/hooks/useMcpManager.js +6 -0
- package/build/cli/hooks/useMcpManager.js.map +1 -1
- package/build/cli/hooks/usePromptCompletion.d.ts +1 -2
- package/build/cli/hooks/usePromptCompletion.js +0 -1
- package/build/cli/hooks/usePromptCompletion.js.map +1 -1
- package/build/cli/hooks/useTransientMessage.d.ts +7 -0
- package/build/cli/hooks/useTransientMessage.js +34 -0
- package/build/cli/hooks/useTransientMessage.js.map +1 -0
- package/build/cli/index.js +7 -3
- package/build/cli/index.js.map +1 -1
- package/build/cli/key/keyBindings.d.ts +0 -19
- package/build/cli/key/keyBindings.js +0 -14
- package/build/cli/key/keyBindings.js.map +1 -1
- package/build/cli/key/keyMatchers.d.ts +0 -9
- package/build/cli/key/keyMatchers.js +0 -8
- package/build/cli/key/keyMatchers.js.map +1 -1
- package/build/cli/key/keybindingUtils.d.ts +3 -0
- package/build/cli/key/keybindingUtils.js +74 -0
- package/build/cli/key/keybindingUtils.js.map +1 -0
- package/build/cli/key/textBuffer/constants.d.ts +1 -0
- package/build/cli/key/textBuffer/constants.js +2 -0
- package/build/cli/key/textBuffer/constants.js.map +1 -0
- package/build/cli/key/textBuffer/index.d.ts +3 -0
- package/build/cli/key/textBuffer/index.js +3 -0
- package/build/cli/key/textBuffer/index.js.map +1 -0
- package/build/cli/key/textBuffer/textBuffer.d.ts +73 -0
- package/build/cli/key/textBuffer/textBuffer.js +1154 -0
- package/build/cli/key/textBuffer/textBuffer.js.map +1 -0
- package/build/cli/key/textBuffer/types.d.ts +106 -0
- package/build/cli/key/textBuffer/types.js +2 -0
- package/build/cli/key/textBuffer/types.js.map +1 -0
- package/build/cli/services/InputOffloadService.d.ts +20 -0
- package/build/cli/services/InputOffloadService.js +105 -0
- package/build/cli/services/InputOffloadService.js.map +1 -0
- package/build/cli/services/McpClientService.js +5 -3
- package/build/cli/services/McpClientService.js.map +1 -1
- package/build/cli/services/McpManager.js +4 -2
- package/build/cli/services/McpManager.js.map +1 -1
- package/build/cli/theme/builtin/tokyonight-dark.js +1 -0
- package/build/cli/theme/builtin/tokyonight-dark.js.map +1 -1
- package/build/cli/theme/semantic-tokens.js +8 -8
- package/build/cli/theme/semantic-tokens.js.map +1 -1
- package/build/cli/theme/theme-manager.js +4 -2
- package/build/cli/theme/theme-manager.js.map +1 -1
- package/build/cli/theme/theme.d.ts +1 -23
- package/build/cli/theme/theme.js +11 -31
- package/build/cli/theme/theme.js.map +1 -1
- package/build/cli/types.d.ts +1 -2
- package/build/cli/ui/CLILogo.d.ts +1 -0
- package/build/cli/ui/CLILogo.js +33 -0
- package/build/cli/ui/CLILogo.js.map +1 -0
- package/build/cli/ui/HistoryItemDisplay.js +36 -1
- package/build/cli/ui/HistoryItemDisplay.js.map +1 -1
- package/build/cli/ui/InputPrompt.d.ts +8 -1
- package/build/cli/ui/InputPrompt.js +142 -24
- package/build/cli/ui/InputPrompt.js.map +1 -1
- package/build/cli/ui/LLMHealthChecker.js +1 -1
- package/build/cli/ui/LLMStatusLoader.d.ts +10 -0
- package/build/cli/ui/LLMStatusLoader.js +35 -0
- package/build/cli/ui/LLMStatusLoader.js.map +1 -0
- package/build/cli/ui/MainContent.js +2 -7
- package/build/cli/ui/MainContent.js.map +1 -1
- package/build/cli/ui/SystemInfoSummaryBox.js +3 -2
- package/build/cli/ui/SystemInfoSummaryBox.js.map +1 -1
- package/build/cli/ui/ThinkingIndicator.d.ts +3 -2
- package/build/cli/ui/ThinkingIndicator.js +3 -3
- package/build/cli/ui/ThinkingIndicator.js.map +1 -1
- package/build/cli/ui/ToastDisplay.d.ts +2 -0
- package/build/cli/ui/ToastDisplay.js +19 -0
- package/build/cli/ui/ToastDisplay.js.map +1 -0
- package/build/cli/ui/shared/ExpandableText.d.ts +0 -3
- package/build/cli/ui/shared/ExpandableText.js +0 -3
- package/build/cli/ui/shared/ExpandableText.js.map +1 -1
- package/build/cli/utils/bootLogo.d.ts +8 -0
- package/build/cli/utils/bootLogo.js +68 -0
- package/build/cli/utils/bootLogo.js.map +1 -0
- package/build/cli/utils/events.d.ts +28 -0
- package/build/cli/utils/events.js +18 -0
- package/build/cli/utils/events.js.map +1 -0
- package/build/cli/utils/highlights.d.ts +8 -0
- package/build/cli/utils/highlights.js +117 -0
- package/build/cli/utils/highlights.js.map +1 -0
- package/build/cli/utils/textUtil.d.ts +5 -0
- package/build/cli/utils/textUtil.js +32 -0
- package/build/cli/utils/textUtil.js.map +1 -1
- package/build/shared/utils/debugLogger.d.ts +4 -0
- package/build/shared/utils/debugLogger.js +12 -0
- package/build/shared/utils/debugLogger.js.map +1 -1
- package/build/utils/cleanup.d.ts +30 -0
- package/build/utils/cleanup.js +137 -0
- package/build/utils/cleanup.js.map +1 -0
- package/package.json +4 -2
- package/build/cli/key/text-buffer.d.ts +0 -156
- package/build/cli/key/text-buffer.js +0 -937
- package/build/cli/key/text-buffer.js.map +0 -1
- package/build/cli/tmp.d.ts +0 -1
- package/build/cli/tmp.js +0 -27
- package/build/cli/tmp.js.map +0 -1
- package/build/cli/ui/layout/DefaultAppLayout.d.ts +0 -2
- package/build/cli/ui/layout/DefaultAppLayout.js +0 -6
- package/build/cli/ui/layout/DefaultAppLayout.js.map +0 -1
|
@@ -0,0 +1,1154 @@
|
|
|
1
|
+
import { LRU_BUFFER_PERF_CACHE_LIMIT } from "../../constants.js";
|
|
2
|
+
import { cpLen, cpSlice, getCachedStringWidth, shiftExpandedRegions, stripUnsafeCharacters, toCodePoints, } from "../../utils/textUtil.js";
|
|
3
|
+
import { LRUCache } from "mnemonist";
|
|
4
|
+
import { useCallback, useEffect, useMemo, useReducer } from "react";
|
|
5
|
+
import { LARGE_PASTE_CHAR_THRESHOLD, LARGE_PASTE_LINE_THRESHOLD, } from "../../../cli/ui/InputPrompt.js";
|
|
6
|
+
import { PASTED_TEXT_PLACEHOLDER_REGEX } from "./constants.js";
|
|
7
|
+
const lineLayoutCache = new LRUCache(LRU_BUFFER_PERF_CACHE_LIMIT);
|
|
8
|
+
const historyLimit = 100;
|
|
9
|
+
function calculateInitialCursorPosition(initialLines, offset) {
|
|
10
|
+
let remainingChars = offset;
|
|
11
|
+
let row = 0;
|
|
12
|
+
while (row < initialLines.length) {
|
|
13
|
+
const lineLength = cpLen(initialLines[row]);
|
|
14
|
+
// Add 1 for the newline character (except for the last line)
|
|
15
|
+
const totalCharsInLineAndNewline = lineLength + (row < initialLines.length - 1 ? 1 : 0);
|
|
16
|
+
if (remainingChars <= lineLength) {
|
|
17
|
+
// Cursor is on this line
|
|
18
|
+
return [row, remainingChars];
|
|
19
|
+
}
|
|
20
|
+
remainingChars -= totalCharsInLineAndNewline;
|
|
21
|
+
row++;
|
|
22
|
+
}
|
|
23
|
+
// Offset is beyond the text, place cursor at the end of the last line
|
|
24
|
+
if (initialLines.length > 0) {
|
|
25
|
+
const lastRow = initialLines.length - 1;
|
|
26
|
+
return [lastRow, cpLen(initialLines[lastRow])];
|
|
27
|
+
}
|
|
28
|
+
return [0, 0]; // Default for empty text
|
|
29
|
+
}
|
|
30
|
+
export const replaceRangeInternal = (state, startRow, startCol, endRow, endCol, text) => {
|
|
31
|
+
const currentLine = (row) => state.lines[row] || "";
|
|
32
|
+
const currentLineLen = (row) => cpLen(currentLine(row));
|
|
33
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
34
|
+
if (startRow > endRow ||
|
|
35
|
+
(startRow === endRow && startCol > endCol) ||
|
|
36
|
+
startRow < 0 ||
|
|
37
|
+
startCol < 0 ||
|
|
38
|
+
endRow >= state.lines.length ||
|
|
39
|
+
(endRow < state.lines.length && endCol > currentLineLen(endRow))) {
|
|
40
|
+
return state; // Invalid range
|
|
41
|
+
}
|
|
42
|
+
const newLines = [...state.lines];
|
|
43
|
+
const sCol = clamp(startCol, 0, currentLineLen(startRow));
|
|
44
|
+
const eCol = clamp(endCol, 0, currentLineLen(endRow));
|
|
45
|
+
const prefix = cpSlice(currentLine(startRow), 0, sCol);
|
|
46
|
+
const suffix = cpSlice(currentLine(endRow), eCol);
|
|
47
|
+
const normalisedReplacement = text
|
|
48
|
+
.replace(/\r\n/g, "\n")
|
|
49
|
+
.replace(/\r/g, "\n");
|
|
50
|
+
const replacementParts = normalisedReplacement.split("\n");
|
|
51
|
+
// The combined first line of the new text
|
|
52
|
+
const firstLine = prefix + replacementParts[0];
|
|
53
|
+
if (replacementParts.length === 1) {
|
|
54
|
+
// No newlines in replacement: combine prefix, replacement, and suffix on one line.
|
|
55
|
+
newLines.splice(startRow, endRow - startRow + 1, firstLine + suffix);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Newlines in replacement: create new lines.
|
|
59
|
+
const lastLine = replacementParts[replacementParts.length - 1] + suffix;
|
|
60
|
+
const middleLines = replacementParts.slice(1, -1);
|
|
61
|
+
newLines.splice(startRow, endRow - startRow + 1, firstLine, ...middleLines, lastLine);
|
|
62
|
+
}
|
|
63
|
+
const finalCursorRow = startRow + replacementParts.length - 1;
|
|
64
|
+
const finalCursorCol = (replacementParts.length > 1 ? 0 : sCol) +
|
|
65
|
+
cpLen(replacementParts[replacementParts.length - 1]);
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
lines: newLines,
|
|
69
|
+
cursorRow: Math.min(Math.max(finalCursorRow, 0), newLines.length - 1),
|
|
70
|
+
cursorCol: Math.max(0, Math.min(finalCursorCol, cpLen(newLines[finalCursorRow] || ""))),
|
|
71
|
+
preferredCol: null,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
export function getExpandedPasteAtLine(lineIndex, expandedPaste) {
|
|
75
|
+
if (expandedPaste &&
|
|
76
|
+
lineIndex >= expandedPaste.startLine &&
|
|
77
|
+
lineIndex < expandedPaste.startLine + expandedPaste.lineCount) {
|
|
78
|
+
return expandedPaste.id;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
export function detachExpandedPaste(state) {
|
|
83
|
+
const expandedId = getExpandedPasteAtLine(state.cursorRow, state.expandedPaste);
|
|
84
|
+
if (!expandedId)
|
|
85
|
+
return state;
|
|
86
|
+
const { [expandedId]: _, ...newPastedContent } = state.pastedContent;
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
expandedPaste: null,
|
|
90
|
+
pastedContent: newPastedContent,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export const pushUndo = (currentState) => {
|
|
94
|
+
const snapshot = {
|
|
95
|
+
lines: [...currentState.lines],
|
|
96
|
+
cursorRow: currentState.cursorRow,
|
|
97
|
+
cursorCol: currentState.cursorCol,
|
|
98
|
+
pastedContent: { ...currentState.pastedContent },
|
|
99
|
+
expandedPaste: currentState.expandedPaste
|
|
100
|
+
? { ...currentState.expandedPaste }
|
|
101
|
+
: null,
|
|
102
|
+
};
|
|
103
|
+
const newStack = [...currentState.undoStack, snapshot];
|
|
104
|
+
if (newStack.length > historyLimit) {
|
|
105
|
+
newStack.shift();
|
|
106
|
+
}
|
|
107
|
+
return { ...currentState, undoStack: newStack, redoStack: [] };
|
|
108
|
+
};
|
|
109
|
+
function generatePastedTextId(content, lineCount, pastedContent) {
|
|
110
|
+
const base = lineCount > LARGE_PASTE_LINE_THRESHOLD
|
|
111
|
+
? `[Pasted Text: ${lineCount} lines]`
|
|
112
|
+
: `[Pasted Text: ${content.length} chars]`;
|
|
113
|
+
let id = base;
|
|
114
|
+
let suffix = 2;
|
|
115
|
+
while (pastedContent[id]) {
|
|
116
|
+
id = base.replace("]", ` #${suffix}]`);
|
|
117
|
+
suffix++;
|
|
118
|
+
}
|
|
119
|
+
return id;
|
|
120
|
+
}
|
|
121
|
+
function normalizePasteContent(content) {
|
|
122
|
+
return stripUnsafeCharacters(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
123
|
+
}
|
|
124
|
+
export function buildExpandedPasteInfo(line, row, transformation, pastedContent) {
|
|
125
|
+
if (transformation.type !== "paste" || !transformation.id) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const pastedText = pastedContent[transformation.id];
|
|
129
|
+
if (!pastedText) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const normalizedContent = normalizePasteContent(pastedText);
|
|
133
|
+
const lineCount = normalizedContent.split("\n").length;
|
|
134
|
+
return {
|
|
135
|
+
id: transformation.id,
|
|
136
|
+
startLine: row,
|
|
137
|
+
lineCount,
|
|
138
|
+
prefix: cpSlice(line, 0, transformation.logStart),
|
|
139
|
+
suffix: cpSlice(line, transformation.logEnd),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function getLineLayoutCacheKey(line, viewportWidth, isCursorOnLine, cursorCol) {
|
|
143
|
+
if (!isCursorOnLine) {
|
|
144
|
+
return `${viewportWidth}:N:${line}`;
|
|
145
|
+
}
|
|
146
|
+
return `${viewportWidth}:C:${cursorCol}:${line}`;
|
|
147
|
+
}
|
|
148
|
+
function calculateVisualCursorFromLayout(layout, logicalCursor) {
|
|
149
|
+
const { logicalToVisualMap, visualLines, transformedToLogicalMaps } = layout;
|
|
150
|
+
const [logicalRow, logicalCol] = logicalCursor;
|
|
151
|
+
const segmentsForLogicalLine = logicalToVisualMap[logicalRow];
|
|
152
|
+
if (!segmentsForLogicalLine || segmentsForLogicalLine.length === 0) {
|
|
153
|
+
return [0, 0, 0];
|
|
154
|
+
}
|
|
155
|
+
let targetSegmentIndex = segmentsForLogicalLine.findIndex(([, startColInLogical], index) => {
|
|
156
|
+
const nextStartColInLogical = index + 1 < segmentsForLogicalLine.length
|
|
157
|
+
? segmentsForLogicalLine[index + 1][1]
|
|
158
|
+
: Infinity;
|
|
159
|
+
return (logicalCol >= startColInLogical && logicalCol < nextStartColInLogical);
|
|
160
|
+
});
|
|
161
|
+
if (targetSegmentIndex === -1) {
|
|
162
|
+
if (logicalCol === 0) {
|
|
163
|
+
targetSegmentIndex = 0;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
targetSegmentIndex = segmentsForLogicalLine.length - 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const [visualRow, startColInLogical] = segmentsForLogicalLine[targetSegmentIndex];
|
|
170
|
+
// Find the coordinates in transformed space in order to conver to visual
|
|
171
|
+
const transformedToLogicalMap = transformedToLogicalMaps[logicalRow] ?? [];
|
|
172
|
+
let transformedCol = 0;
|
|
173
|
+
for (let i = 0; i < transformedToLogicalMap.length; i++) {
|
|
174
|
+
if (transformedToLogicalMap[i] > logicalCol) {
|
|
175
|
+
transformedCol = Math.max(0, i - 1);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
if (i === transformedToLogicalMap.length - 1) {
|
|
179
|
+
transformedCol = transformedToLogicalMap.length - 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
let startColInTransformed = 0;
|
|
183
|
+
while (startColInTransformed < transformedToLogicalMap.length &&
|
|
184
|
+
transformedToLogicalMap[startColInTransformed] < startColInLogical) {
|
|
185
|
+
startColInTransformed++;
|
|
186
|
+
}
|
|
187
|
+
const clampedTransformedCol = Math.min(transformedCol, Math.max(0, transformedToLogicalMap.length - 1));
|
|
188
|
+
const visualColIndex = clampedTransformedCol - startColInTransformed;
|
|
189
|
+
const currentVisualLineText = visualLines[visualRow] ?? "";
|
|
190
|
+
let visualColWidth = 0;
|
|
191
|
+
const codePoints = toCodePoints(currentVisualLineText);
|
|
192
|
+
for (let i = 0; i < Math.min(visualColIndex, codePoints.length); i++) {
|
|
193
|
+
visualColWidth += getCachedStringWidth(codePoints[i]);
|
|
194
|
+
}
|
|
195
|
+
const clampedVisualColIndex = Math.min(Math.max(visualColIndex, 0), codePoints.length);
|
|
196
|
+
return [visualRow, clampedVisualColIndex, visualColWidth];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Helper: Converts logical row/col position to absolute text offset
|
|
200
|
+
*/
|
|
201
|
+
export function logicalPosToOffset(lines, row, col) {
|
|
202
|
+
let offset = 0;
|
|
203
|
+
const actualRow = Math.min(row, lines.length - 1);
|
|
204
|
+
for (let i = 0; i < actualRow; i++) {
|
|
205
|
+
offset += cpLen(lines[i]) + 1; // +1 for newline
|
|
206
|
+
}
|
|
207
|
+
if (actualRow >= 0 && actualRow < lines.length) {
|
|
208
|
+
offset += Math.min(col, cpLen(lines[actualRow]));
|
|
209
|
+
}
|
|
210
|
+
return offset;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Helper: Converts absolute text offset to logical row/col position
|
|
214
|
+
*/
|
|
215
|
+
export function offsetToLogicalPos(text, offset) {
|
|
216
|
+
const lines = text.split("\n");
|
|
217
|
+
let currentOffset = 0;
|
|
218
|
+
for (let i = 0; i < lines.length; i++) {
|
|
219
|
+
const lineLen = cpLen(lines[i]);
|
|
220
|
+
const lineLenWithNewline = lineLen + (i < lines.length - 1 ? 1 : 0);
|
|
221
|
+
if (offset <= currentOffset + lineLen) {
|
|
222
|
+
return [i, offset - currentOffset];
|
|
223
|
+
}
|
|
224
|
+
if (offset < currentOffset + lineLenWithNewline) {
|
|
225
|
+
return [i, lineLen];
|
|
226
|
+
}
|
|
227
|
+
currentOffset += lineLenWithNewline;
|
|
228
|
+
}
|
|
229
|
+
const lastRow = Math.max(0, lines.length - 1);
|
|
230
|
+
return [lastRow, cpLen(lines[lastRow] || "")];
|
|
231
|
+
}
|
|
232
|
+
function bufferReducerLogic(state, action) {
|
|
233
|
+
const { lines, cursorRow, cursorCol } = state;
|
|
234
|
+
const currentLineText = lines[cursorRow] || "";
|
|
235
|
+
const lineCount = lines.length;
|
|
236
|
+
const pushUndoLocal = pushUndo;
|
|
237
|
+
const currentLine = (r) => state.lines[r] ?? "";
|
|
238
|
+
switch (action.type) {
|
|
239
|
+
case "SET_VIEWPORT":
|
|
240
|
+
return {
|
|
241
|
+
...state,
|
|
242
|
+
viewportWidth: action.payload.width,
|
|
243
|
+
viewportHeight: action.payload.height,
|
|
244
|
+
};
|
|
245
|
+
case "SET_TEXT": {
|
|
246
|
+
let nextState = state;
|
|
247
|
+
if (action.pushToUndo !== false) {
|
|
248
|
+
nextState = pushUndoLocal(state);
|
|
249
|
+
}
|
|
250
|
+
const newContentLines = action.payload
|
|
251
|
+
.replace(/\r\n?/g, "\n")
|
|
252
|
+
.split("\n");
|
|
253
|
+
const lines = newContentLines.length === 0 ? [""] : newContentLines;
|
|
254
|
+
let newCursorRow;
|
|
255
|
+
let newCursorCol;
|
|
256
|
+
if (typeof action.cursorPosition === "number") {
|
|
257
|
+
[newCursorRow, newCursorCol] = offsetToLogicalPos(action.payload, action.cursorPosition);
|
|
258
|
+
}
|
|
259
|
+
else if (action.cursorPosition === "start") {
|
|
260
|
+
newCursorRow = 0;
|
|
261
|
+
newCursorCol = 0;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Default to 'end'
|
|
265
|
+
newCursorRow = lines.length - 1;
|
|
266
|
+
newCursorCol = cpLen(lines[newCursorRow] ?? "");
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
...nextState,
|
|
270
|
+
lines,
|
|
271
|
+
cursorRow: newCursorRow,
|
|
272
|
+
cursorCol: newCursorCol,
|
|
273
|
+
preferredCol: null,
|
|
274
|
+
pastedContent: action.payload === "" ? {} : nextState.pastedContent,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
case "INSERT": {
|
|
278
|
+
const nextState = detachExpandedPaste(pushUndoLocal(state));
|
|
279
|
+
const newLines = [...nextState.lines];
|
|
280
|
+
let newCursorRow = nextState.cursorRow;
|
|
281
|
+
let newCursorCol = nextState.cursorCol;
|
|
282
|
+
let payload = action.payload;
|
|
283
|
+
let newPastedContent = nextState.pastedContent;
|
|
284
|
+
if (action.isPaste) {
|
|
285
|
+
payload = payload.replace(/\r\n|\r/g, "\n");
|
|
286
|
+
const lineCount = payload.split("\n").length;
|
|
287
|
+
if (lineCount > LARGE_PASTE_LINE_THRESHOLD ||
|
|
288
|
+
payload.length > LARGE_PASTE_CHAR_THRESHOLD) {
|
|
289
|
+
const id = generatePastedTextId(payload, lineCount, newPastedContent);
|
|
290
|
+
newPastedContent = {
|
|
291
|
+
...newPastedContent,
|
|
292
|
+
[id]: payload,
|
|
293
|
+
};
|
|
294
|
+
payload = id;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (payload.length === 0) {
|
|
298
|
+
return state;
|
|
299
|
+
}
|
|
300
|
+
const str = stripUnsafeCharacters(payload.replace(/\r\n/g, "\n").replace(/\r/g, "\n"));
|
|
301
|
+
const parts = str.split("\n");
|
|
302
|
+
const lineContent = currentLine(newCursorRow);
|
|
303
|
+
const before = cpSlice(lineContent, 0, newCursorCol);
|
|
304
|
+
const after = cpSlice(lineContent, newCursorCol);
|
|
305
|
+
let lineDelta = 0;
|
|
306
|
+
if (parts.length > 1) {
|
|
307
|
+
newLines[newCursorRow] = before + parts[0];
|
|
308
|
+
const remainingParts = parts.slice(1);
|
|
309
|
+
const lastPartOriginal = remainingParts.pop() ?? "";
|
|
310
|
+
newLines.splice(newCursorRow + 1, 0, ...remainingParts);
|
|
311
|
+
newLines.splice(newCursorRow + parts.length - 1, 0, lastPartOriginal + after);
|
|
312
|
+
lineDelta = parts.length - 1;
|
|
313
|
+
newCursorRow = newCursorRow + parts.length - 1;
|
|
314
|
+
newCursorCol = cpLen(lastPartOriginal);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
newLines[newCursorRow] = before + parts[0] + after;
|
|
318
|
+
newCursorCol = cpLen(before) + cpLen(parts[0]);
|
|
319
|
+
}
|
|
320
|
+
const { newInfo: newExpandedPaste, isDetached } = shiftExpandedRegions(nextState.expandedPaste, nextState.cursorRow, lineDelta);
|
|
321
|
+
if (isDetached && newExpandedPaste === null && nextState.expandedPaste) {
|
|
322
|
+
delete newPastedContent[nextState.expandedPaste.id];
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
...nextState,
|
|
326
|
+
lines: newLines,
|
|
327
|
+
cursorRow: newCursorRow,
|
|
328
|
+
cursorCol: newCursorCol,
|
|
329
|
+
preferredCol: null,
|
|
330
|
+
pastedContent: newPastedContent,
|
|
331
|
+
expandedPaste: newExpandedPaste,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
case "TOGGLE_PASTE_EXPANSION": {
|
|
335
|
+
const { id, row, col } = action.payload;
|
|
336
|
+
if (state.expandedPaste?.id === id) {
|
|
337
|
+
return {
|
|
338
|
+
...state,
|
|
339
|
+
expandedPaste: null,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const transform = getTransformUnderCursor(row, col, state.transformationsByLine, { includeEdge: true });
|
|
343
|
+
if (!transform || transform.type !== "paste" || transform.id !== id) {
|
|
344
|
+
return state;
|
|
345
|
+
}
|
|
346
|
+
const expandedPaste = buildExpandedPasteInfo(state.lines[row] ?? "", row, transform, state.pastedContent);
|
|
347
|
+
if (!expandedPaste) {
|
|
348
|
+
return state;
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
...state,
|
|
352
|
+
expandedPaste,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
case "NEWLINE": {
|
|
356
|
+
const nextState = pushUndoLocal(state);
|
|
357
|
+
const newLines = [...nextState.lines];
|
|
358
|
+
const before = cpSlice(currentLineText, 0, cursorCol);
|
|
359
|
+
const after = cpSlice(currentLineText, cursorCol);
|
|
360
|
+
newLines[cursorRow] = before;
|
|
361
|
+
newLines.splice(cursorRow + 1, 0, after);
|
|
362
|
+
return {
|
|
363
|
+
...state,
|
|
364
|
+
lines: newLines,
|
|
365
|
+
cursorRow: cursorRow + 1,
|
|
366
|
+
cursorCol: 0,
|
|
367
|
+
preferredCol: null,
|
|
368
|
+
redoStack: [],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
case "BACKSPACE": {
|
|
372
|
+
if (cursorCol === 0 && cursorRow === 0) {
|
|
373
|
+
return state;
|
|
374
|
+
}
|
|
375
|
+
const nextState = pushUndoLocal(state);
|
|
376
|
+
const newLines = [...nextState.lines];
|
|
377
|
+
if (cursorCol > 0) {
|
|
378
|
+
newLines[cursorRow] =
|
|
379
|
+
cpSlice(currentLineText, 0, cursorCol - 1) +
|
|
380
|
+
cpSlice(currentLineText, cursorCol);
|
|
381
|
+
return {
|
|
382
|
+
...nextState,
|
|
383
|
+
lines: newLines,
|
|
384
|
+
cursorCol: cursorCol - 1,
|
|
385
|
+
preferredCol: null,
|
|
386
|
+
redoStack: [],
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
const prevLine = lines[cursorRow - 1];
|
|
391
|
+
const prevLen = cpLen(prevLine);
|
|
392
|
+
newLines[cursorRow - 1] = prevLine + currentLineText;
|
|
393
|
+
newLines.splice(cursorRow, 1);
|
|
394
|
+
return {
|
|
395
|
+
...nextState,
|
|
396
|
+
lines: newLines,
|
|
397
|
+
cursorRow: cursorRow - 1,
|
|
398
|
+
cursorCol: prevLen,
|
|
399
|
+
preferredCol: null,
|
|
400
|
+
redoStack: [],
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
case "DELETE": {
|
|
405
|
+
if (cursorCol === cpLen(currentLineText) && cursorRow === lineCount - 1)
|
|
406
|
+
return state;
|
|
407
|
+
const nextState = pushUndoLocal(state);
|
|
408
|
+
const newLines = [...nextState.lines];
|
|
409
|
+
if (cursorCol < cpLen(currentLineText)) {
|
|
410
|
+
newLines[cursorRow] =
|
|
411
|
+
cpSlice(currentLineText, 0, cursorCol) +
|
|
412
|
+
cpSlice(currentLineText, cursorCol + 1);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
newLines[cursorRow] = currentLineText + lines[cursorRow + 1];
|
|
416
|
+
newLines.splice(cursorRow + 1, 1);
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
...nextState,
|
|
420
|
+
lines: newLines,
|
|
421
|
+
preferredCol: null,
|
|
422
|
+
redoStack: [],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
case "KILL_LINE_RIGHT": {
|
|
426
|
+
const nextState = pushUndoLocal(state);
|
|
427
|
+
const newLines = [...nextState.lines];
|
|
428
|
+
newLines[cursorRow] = cpSlice(currentLineText, 0, cursorCol);
|
|
429
|
+
return {
|
|
430
|
+
...nextState,
|
|
431
|
+
lines: newLines,
|
|
432
|
+
preferredCol: null,
|
|
433
|
+
redoStack: [],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
case "KILL_LINE_LEFT": {
|
|
437
|
+
const nextState = pushUndoLocal(state);
|
|
438
|
+
const newLines = [...nextState.lines];
|
|
439
|
+
newLines[cursorRow] = cpSlice(currentLineText, cursorCol);
|
|
440
|
+
return {
|
|
441
|
+
...nextState,
|
|
442
|
+
lines: newLines,
|
|
443
|
+
cursorCol: 0,
|
|
444
|
+
preferredCol: null,
|
|
445
|
+
redoStack: [],
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
case "DELETE_WORD_LEFT": {
|
|
449
|
+
if (cursorCol === 0)
|
|
450
|
+
return state;
|
|
451
|
+
const before = cpSlice(currentLineText, 0, cursorCol);
|
|
452
|
+
const after = cpSlice(currentLineText, cursorCol);
|
|
453
|
+
// Simple word boundary: skip whitespace then skip non-whitespace
|
|
454
|
+
let i = before.length - 1;
|
|
455
|
+
while (i >= 0 && before[i] === " ")
|
|
456
|
+
i--;
|
|
457
|
+
while (i >= 0 && before[i] !== " ")
|
|
458
|
+
i--;
|
|
459
|
+
const nextState = pushUndoLocal(state);
|
|
460
|
+
const newBefore = before.slice(0, i + 1);
|
|
461
|
+
const newLines = [...nextState.lines];
|
|
462
|
+
newLines[cursorRow] = newBefore + after;
|
|
463
|
+
return {
|
|
464
|
+
...nextState,
|
|
465
|
+
lines: newLines,
|
|
466
|
+
cursorCol: cpLen(newBefore),
|
|
467
|
+
preferredCol: null,
|
|
468
|
+
redoStack: [],
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
case "DELETE_WORD_RIGHT": {
|
|
472
|
+
const before = cpSlice(currentLineText, 0, cursorCol);
|
|
473
|
+
const after = cpSlice(currentLineText, cursorCol);
|
|
474
|
+
if (after.length === 0)
|
|
475
|
+
return state;
|
|
476
|
+
let i = 0;
|
|
477
|
+
while (i < after.length && after[i] === " ")
|
|
478
|
+
i++;
|
|
479
|
+
while (i < after.length && after[i] !== " ")
|
|
480
|
+
i++;
|
|
481
|
+
const nextState = pushUndoLocal(state);
|
|
482
|
+
const newAfter = after.slice(i);
|
|
483
|
+
const newLines = [...nextState.lines];
|
|
484
|
+
newLines[cursorRow] = before + newAfter;
|
|
485
|
+
return {
|
|
486
|
+
...nextState,
|
|
487
|
+
lines: newLines,
|
|
488
|
+
preferredCol: null,
|
|
489
|
+
redoStack: [],
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
case "MOVE": {
|
|
493
|
+
const { dir } = action;
|
|
494
|
+
const { visualLayout, preferredCol } = state;
|
|
495
|
+
const [vRow, , vWidth] = calculateVisualCursorFromLayout(visualLayout, [
|
|
496
|
+
cursorRow,
|
|
497
|
+
cursorCol,
|
|
498
|
+
]);
|
|
499
|
+
if (dir === "left" ||
|
|
500
|
+
dir === "right" ||
|
|
501
|
+
dir === "home" ||
|
|
502
|
+
dir === "end") {
|
|
503
|
+
let r = cursorRow;
|
|
504
|
+
let c = cursorCol;
|
|
505
|
+
if (dir === "left") {
|
|
506
|
+
if (c > 0)
|
|
507
|
+
c--;
|
|
508
|
+
else if (r > 0) {
|
|
509
|
+
r--;
|
|
510
|
+
c = cpLen(lines[r]);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else if (dir === "right") {
|
|
514
|
+
if (c < cpLen(currentLineText))
|
|
515
|
+
c++;
|
|
516
|
+
else if (r < lineCount - 1) {
|
|
517
|
+
r++;
|
|
518
|
+
c = 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else if (dir === "home")
|
|
522
|
+
c = 0;
|
|
523
|
+
else if (dir === "end")
|
|
524
|
+
c = cpLen(currentLineText);
|
|
525
|
+
return { ...state, cursorRow: r, cursorCol: c, preferredCol: null };
|
|
526
|
+
}
|
|
527
|
+
let newVisRow = vRow;
|
|
528
|
+
const targetWidth = preferredCol !== null ? preferredCol : vWidth;
|
|
529
|
+
if (dir === "up" && newVisRow > 0) {
|
|
530
|
+
newVisRow--;
|
|
531
|
+
}
|
|
532
|
+
else if (dir === "down" &&
|
|
533
|
+
newVisRow < visualLayout.visualLines.length - 1) {
|
|
534
|
+
newVisRow++;
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
return state;
|
|
538
|
+
}
|
|
539
|
+
const newLineText = visualLayout.visualLines[newVisRow] ?? "";
|
|
540
|
+
const codePoints = toCodePoints(newLineText);
|
|
541
|
+
let currentWidth = 0;
|
|
542
|
+
let newVisColIdx = 0;
|
|
543
|
+
for (let i = 0; i < codePoints.length; i++) {
|
|
544
|
+
const charWidth = getCachedStringWidth(codePoints[i]);
|
|
545
|
+
if (currentWidth + charWidth > targetWidth) {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
currentWidth += charWidth;
|
|
549
|
+
newVisColIdx = i + 1;
|
|
550
|
+
}
|
|
551
|
+
const mapping = visualLayout.visualToLogicalMap[newVisRow];
|
|
552
|
+
if (mapping) {
|
|
553
|
+
return {
|
|
554
|
+
...state,
|
|
555
|
+
cursorRow: mapping[0],
|
|
556
|
+
cursorCol: mapping[1] + newVisColIdx,
|
|
557
|
+
preferredCol: targetWidth,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return state;
|
|
561
|
+
}
|
|
562
|
+
case "REPLACE_RANGE": {
|
|
563
|
+
const { startRow, startCol, endRow, endCol, text } = action.payload;
|
|
564
|
+
const nextState = pushUndoLocal(state);
|
|
565
|
+
const newState = replaceRangeInternal(nextState, startRow, startCol, endRow, endCol, text);
|
|
566
|
+
const oldLineCount = endRow - startRow + 1;
|
|
567
|
+
const newLineCount = newState.lines.length - (nextState.lines.length - oldLineCount);
|
|
568
|
+
const lineDelta = newLineCount - oldLineCount;
|
|
569
|
+
const { newInfo: newExpandedPaste, isDetached } = shiftExpandedRegions(nextState.expandedPaste, startRow, lineDelta, endRow);
|
|
570
|
+
const newPastedContent = { ...newState.pastedContent };
|
|
571
|
+
if (isDetached && nextState.expandedPaste) {
|
|
572
|
+
delete newPastedContent[nextState.expandedPaste.id];
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
...newState,
|
|
576
|
+
pastedContent: newPastedContent,
|
|
577
|
+
expandedPaste: newExpandedPaste,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
case "UNDO": {
|
|
581
|
+
const stateToRestore = state.undoStack[state.undoStack.length - 1];
|
|
582
|
+
if (!stateToRestore)
|
|
583
|
+
return state;
|
|
584
|
+
const currentSnapshot = {
|
|
585
|
+
lines: [...state.lines],
|
|
586
|
+
cursorRow: state.cursorRow,
|
|
587
|
+
cursorCol: state.cursorCol,
|
|
588
|
+
pastedContent: { ...state.pastedContent },
|
|
589
|
+
expandedPaste: state.expandedPaste ? { ...state.expandedPaste } : null,
|
|
590
|
+
};
|
|
591
|
+
return {
|
|
592
|
+
...state,
|
|
593
|
+
...stateToRestore,
|
|
594
|
+
undoStack: state.undoStack.slice(0, -1),
|
|
595
|
+
redoStack: [...state.redoStack, currentSnapshot],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
case "REDO": {
|
|
599
|
+
const stateToRestore = state.redoStack[state.redoStack.length - 1];
|
|
600
|
+
if (!stateToRestore)
|
|
601
|
+
return state;
|
|
602
|
+
const currentSnapshot = {
|
|
603
|
+
lines: [...state.lines],
|
|
604
|
+
cursorRow: state.cursorRow,
|
|
605
|
+
cursorCol: state.cursorCol,
|
|
606
|
+
pastedContent: { ...state.pastedContent },
|
|
607
|
+
expandedPaste: state.expandedPaste ? { ...state.expandedPaste } : null,
|
|
608
|
+
};
|
|
609
|
+
return {
|
|
610
|
+
...state,
|
|
611
|
+
...stateToRestore,
|
|
612
|
+
redoStack: state.redoStack.slice(0, -1),
|
|
613
|
+
undoStack: [...state.undoStack, currentSnapshot],
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
default:
|
|
617
|
+
return state;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const transformationsCache = new LRUCache(LRU_BUFFER_PERF_CACHE_LIMIT);
|
|
621
|
+
export function calculateTransformationsForLine(line, pastedContent = {}, expandedPaste = null, lineIndex) {
|
|
622
|
+
const hasPastePlaceholder = line.includes("[Pasted Text:");
|
|
623
|
+
const shouldBypassCache = hasPastePlaceholder &&
|
|
624
|
+
expandedPaste !== null &&
|
|
625
|
+
lineIndex === expandedPaste.startLine;
|
|
626
|
+
const cached = !shouldBypassCache
|
|
627
|
+
? transformationsCache.get(line)
|
|
628
|
+
: undefined;
|
|
629
|
+
if (cached !== undefined) {
|
|
630
|
+
return cached;
|
|
631
|
+
}
|
|
632
|
+
const transformations = [];
|
|
633
|
+
const pasteRegex = new RegExp(PASTED_TEXT_PLACEHOLDER_REGEX.source, "g");
|
|
634
|
+
let match;
|
|
635
|
+
match = pasteRegex.exec(line);
|
|
636
|
+
while (match !== null) {
|
|
637
|
+
const logicalText = match[0];
|
|
638
|
+
const logStart = cpLen(line.substring(0, match.index));
|
|
639
|
+
const logEnd = logStart + cpLen(logicalText);
|
|
640
|
+
const isExpanded = expandedPaste?.id === logicalText &&
|
|
641
|
+
lineIndex === expandedPaste.startLine;
|
|
642
|
+
transformations.push({
|
|
643
|
+
logStart,
|
|
644
|
+
logEnd,
|
|
645
|
+
logicalText,
|
|
646
|
+
collapsedText: logicalText,
|
|
647
|
+
expandedText: isExpanded
|
|
648
|
+
? normalizePasteContent(pastedContent[logicalText] ?? logicalText)
|
|
649
|
+
: undefined,
|
|
650
|
+
type: "paste",
|
|
651
|
+
id: logicalText,
|
|
652
|
+
});
|
|
653
|
+
match = pasteRegex.exec(line);
|
|
654
|
+
}
|
|
655
|
+
transformations.sort((a, b) => a.logStart - b.logStart);
|
|
656
|
+
if (!shouldBypassCache) {
|
|
657
|
+
transformationsCache.set(line, transformations);
|
|
658
|
+
}
|
|
659
|
+
return transformations;
|
|
660
|
+
}
|
|
661
|
+
export function calculateTransformedLine(logLine, logIndex, logicalCursor, transformations, expandedPaste = null) {
|
|
662
|
+
let transformedLine = "";
|
|
663
|
+
const transformedToLogMap = [];
|
|
664
|
+
let lastLogPos = 0;
|
|
665
|
+
const cursorIsOnThisLine = logIndex === logicalCursor[0];
|
|
666
|
+
const cursorCol = logicalCursor[1];
|
|
667
|
+
for (const transform of transformations) {
|
|
668
|
+
const textBeforeTransformation = cpSlice(logLine, lastLogPos, transform.logStart);
|
|
669
|
+
transformedLine += textBeforeTransformation;
|
|
670
|
+
for (let i = 0; i < cpLen(textBeforeTransformation); i++) {
|
|
671
|
+
transformedToLogMap.push(lastLogPos + i);
|
|
672
|
+
}
|
|
673
|
+
const isExpanded = (transform.type === "image" &&
|
|
674
|
+
cursorIsOnThisLine &&
|
|
675
|
+
cursorCol >= transform.logStart &&
|
|
676
|
+
cursorCol <= transform.logEnd) ||
|
|
677
|
+
(transform.type === "paste" &&
|
|
678
|
+
transform.id === expandedPaste?.id &&
|
|
679
|
+
logIndex === expandedPaste?.startLine);
|
|
680
|
+
const transformedText = isExpanded
|
|
681
|
+
? (transform.expandedText ?? transform.logicalText)
|
|
682
|
+
: transform.collapsedText;
|
|
683
|
+
transformedLine += transformedText;
|
|
684
|
+
const transformedCodePoints = toCodePoints(transformedText);
|
|
685
|
+
const transformedLen = transformedCodePoints.length;
|
|
686
|
+
if (isExpanded) {
|
|
687
|
+
const logicalLength = Math.max(0, transform.logEnd - transform.logStart);
|
|
688
|
+
const visibleCodePointCount = transformedCodePoints.filter((codePoint) => codePoint !== "\n").length;
|
|
689
|
+
let visibleCodePointIndex = 0;
|
|
690
|
+
for (const codePoint of transformedCodePoints) {
|
|
691
|
+
if (codePoint === "\n") {
|
|
692
|
+
transformedToLogMap.push(transform.logEnd);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const transformationToLogicalOffset = visibleCodePointCount === 0 || logicalLength === 0
|
|
696
|
+
? 0
|
|
697
|
+
: Math.floor((visibleCodePointIndex * logicalLength) / visibleCodePointCount);
|
|
698
|
+
transformedToLogMap.push(transform.logStart +
|
|
699
|
+
Math.min(transformationToLogicalOffset, Math.max(logicalLength - 1, 0)));
|
|
700
|
+
visibleCodePointIndex++;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
const logicalLength = Math.max(0, transform.logEnd - transform.logStart);
|
|
705
|
+
for (let i = 0; i < transformedLen; i++) {
|
|
706
|
+
const transformationToLogicalOffset = logicalLength === 0
|
|
707
|
+
? 0
|
|
708
|
+
: Math.floor((i * logicalLength) / transformedLen);
|
|
709
|
+
const transformationToLogicalIndex = transform.logStart +
|
|
710
|
+
Math.min(transformationToLogicalOffset, Math.max(logicalLength - 1, 0));
|
|
711
|
+
transformedToLogMap.push(transformationToLogicalIndex);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
lastLogPos = transform.logEnd;
|
|
715
|
+
}
|
|
716
|
+
// Append text after last transform
|
|
717
|
+
const remainingUntransformedText = cpSlice(logLine, lastLogPos);
|
|
718
|
+
transformedLine += remainingUntransformedText;
|
|
719
|
+
for (let i = 0; i < cpLen(remainingUntransformedText); i++) {
|
|
720
|
+
transformedToLogMap.push(lastLogPos + i);
|
|
721
|
+
}
|
|
722
|
+
// For a cursor at the very end of the transformed line
|
|
723
|
+
transformedToLogMap.push(cpLen(logLine));
|
|
724
|
+
return { transformedLine, transformedToLogMap };
|
|
725
|
+
}
|
|
726
|
+
function layoutWrappedLine(codePointsInLine, transformedToLogMap, viewportWidth, logIndex) {
|
|
727
|
+
const lineVisualLines = [];
|
|
728
|
+
const lineLogicalToVisualMap = [];
|
|
729
|
+
const lineVisualToLogicalMap = [];
|
|
730
|
+
const lineVisualToTransformedMap = [];
|
|
731
|
+
if (codePointsInLine.length === 0) {
|
|
732
|
+
lineLogicalToVisualMap.push([0, transformedToLogMap[0] ?? 0]);
|
|
733
|
+
lineVisualToLogicalMap.push([logIndex, transformedToLogMap[0] ?? 0]);
|
|
734
|
+
lineVisualToTransformedMap.push(0);
|
|
735
|
+
lineVisualLines.push("");
|
|
736
|
+
return {
|
|
737
|
+
visualLines: lineVisualLines,
|
|
738
|
+
logicalToVisualMap: lineLogicalToVisualMap,
|
|
739
|
+
visualToLogicalMap: lineVisualToLogicalMap,
|
|
740
|
+
visualToTransformedMap: lineVisualToTransformedMap,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
let currentPosInLine = 0;
|
|
744
|
+
while (currentPosInLine < codePointsInLine.length) {
|
|
745
|
+
let currentChunk = "";
|
|
746
|
+
let currentChunkVisualWidth = 0;
|
|
747
|
+
let numCodePointsInChunk = 0;
|
|
748
|
+
let lastWordBreakPoint = -1;
|
|
749
|
+
let numCodePointsAtLastWordBreak = 0;
|
|
750
|
+
for (let i = currentPosInLine; i < codePointsInLine.length; i++) {
|
|
751
|
+
const char = codePointsInLine[i];
|
|
752
|
+
const charVisualWidth = getCachedStringWidth(char);
|
|
753
|
+
if (currentChunkVisualWidth + charVisualWidth > viewportWidth) {
|
|
754
|
+
if (lastWordBreakPoint !== -1 &&
|
|
755
|
+
numCodePointsAtLastWordBreak > 0 &&
|
|
756
|
+
currentPosInLine + numCodePointsAtLastWordBreak < i) {
|
|
757
|
+
currentChunk = codePointsInLine
|
|
758
|
+
.slice(currentPosInLine, currentPosInLine + numCodePointsAtLastWordBreak)
|
|
759
|
+
.join("");
|
|
760
|
+
numCodePointsInChunk = numCodePointsAtLastWordBreak;
|
|
761
|
+
}
|
|
762
|
+
else if (numCodePointsInChunk === 0 &&
|
|
763
|
+
charVisualWidth > viewportWidth) {
|
|
764
|
+
currentChunk = char;
|
|
765
|
+
numCodePointsInChunk = 1;
|
|
766
|
+
}
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
currentChunk += char;
|
|
770
|
+
currentChunkVisualWidth += charVisualWidth;
|
|
771
|
+
numCodePointsInChunk++;
|
|
772
|
+
if (char === " ") {
|
|
773
|
+
lastWordBreakPoint = i;
|
|
774
|
+
numCodePointsAtLastWordBreak = numCodePointsInChunk - 1;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (numCodePointsInChunk === 0 &&
|
|
778
|
+
currentPosInLine < codePointsInLine.length) {
|
|
779
|
+
currentChunk = codePointsInLine[currentPosInLine] ?? "";
|
|
780
|
+
numCodePointsInChunk = 1;
|
|
781
|
+
}
|
|
782
|
+
const logicalStartCol = transformedToLogMap[currentPosInLine] ?? 0;
|
|
783
|
+
lineLogicalToVisualMap.push([lineVisualLines.length, logicalStartCol]);
|
|
784
|
+
lineVisualToLogicalMap.push([logIndex, logicalStartCol]);
|
|
785
|
+
lineVisualToTransformedMap.push(currentPosInLine);
|
|
786
|
+
lineVisualLines.push(currentChunk);
|
|
787
|
+
const logicalStartOfThisChunk = currentPosInLine;
|
|
788
|
+
currentPosInLine += numCodePointsInChunk;
|
|
789
|
+
if (logicalStartOfThisChunk + numCodePointsInChunk <
|
|
790
|
+
codePointsInLine.length &&
|
|
791
|
+
currentPosInLine < codePointsInLine.length &&
|
|
792
|
+
codePointsInLine[currentPosInLine] === " ") {
|
|
793
|
+
currentPosInLine++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
visualLines: lineVisualLines,
|
|
798
|
+
logicalToVisualMap: lineLogicalToVisualMap,
|
|
799
|
+
visualToLogicalMap: lineVisualToLogicalMap,
|
|
800
|
+
visualToTransformedMap: lineVisualToTransformedMap,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function calculateLayout(logicalLines, viewportWidth, logicalCursor, pastedContent, expandedPaste) {
|
|
804
|
+
const visualLines = [];
|
|
805
|
+
const logicalToVisualMap = [];
|
|
806
|
+
const visualToLogicalMap = [];
|
|
807
|
+
const transformedToLogicalMaps = [];
|
|
808
|
+
const visualToTransformedMap = [];
|
|
809
|
+
logicalLines.forEach((logLine, logIndex) => {
|
|
810
|
+
logicalToVisualMap[logIndex] = [];
|
|
811
|
+
const isCursorOnLine = logIndex === logicalCursor[0];
|
|
812
|
+
const isExpandedLine = expandedPaste?.startLine === logIndex;
|
|
813
|
+
const cacheKey = getLineLayoutCacheKey(logLine, viewportWidth, isCursorOnLine, logicalCursor[1]);
|
|
814
|
+
const cached = isExpandedLine ? undefined : lineLayoutCache.get(cacheKey);
|
|
815
|
+
if (cached) {
|
|
816
|
+
const visualLineOffset = visualLines.length;
|
|
817
|
+
visualLines.push(...cached.visualLines);
|
|
818
|
+
cached.logicalToVisualMap.forEach(([relVisualIdx, logCol]) => {
|
|
819
|
+
logicalToVisualMap[logIndex].push([
|
|
820
|
+
visualLineOffset + relVisualIdx,
|
|
821
|
+
logCol,
|
|
822
|
+
]);
|
|
823
|
+
});
|
|
824
|
+
cached.visualToLogicalMap.forEach(([, logCol]) => {
|
|
825
|
+
visualToLogicalMap.push([logIndex, logCol]);
|
|
826
|
+
});
|
|
827
|
+
transformedToLogicalMaps[logIndex] = cached.transformedToLogMap;
|
|
828
|
+
visualToTransformedMap.push(...cached.visualToTransformedMap);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const transformations = calculateTransformationsForLine(logLine, pastedContent, expandedPaste, logIndex);
|
|
832
|
+
const { transformedLine, transformedToLogMap } = calculateTransformedLine(logLine, logIndex, logicalCursor, transformations, expandedPaste);
|
|
833
|
+
const lineVisualLines = [];
|
|
834
|
+
const lineLogicalToVisualMap = [];
|
|
835
|
+
const lineVisualToLogicalMap = [];
|
|
836
|
+
const lineVisualToTransformedMap = [];
|
|
837
|
+
if (transformedLine.length === 0) {
|
|
838
|
+
lineLogicalToVisualMap.push([0, 0]);
|
|
839
|
+
lineVisualToLogicalMap.push([logIndex, 0]);
|
|
840
|
+
lineVisualToTransformedMap.push(0);
|
|
841
|
+
lineVisualLines.push("");
|
|
842
|
+
}
|
|
843
|
+
else {
|
|
844
|
+
const transformedCodePoints = toCodePoints(transformedLine);
|
|
845
|
+
let segmentStart = 0;
|
|
846
|
+
for (let i = 0; i <= transformedCodePoints.length; i++) {
|
|
847
|
+
const isLineBreak = i === transformedCodePoints.length ||
|
|
848
|
+
transformedCodePoints[i] === "\n";
|
|
849
|
+
if (!isLineBreak) {
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const subLineCodePoints = transformedCodePoints.slice(segmentStart, i);
|
|
853
|
+
const subLineMap = transformedToLogMap.slice(segmentStart, i + 1);
|
|
854
|
+
const segmentLayout = layoutWrappedLine(subLineCodePoints, subLineMap, viewportWidth, logIndex);
|
|
855
|
+
segmentLayout.logicalToVisualMap.forEach(([visualIndex, logicalCol]) => {
|
|
856
|
+
lineLogicalToVisualMap.push([
|
|
857
|
+
lineVisualLines.length + visualIndex,
|
|
858
|
+
logicalCol,
|
|
859
|
+
]);
|
|
860
|
+
});
|
|
861
|
+
lineVisualToLogicalMap.push(...segmentLayout.visualToLogicalMap);
|
|
862
|
+
segmentLayout.visualToTransformedMap.forEach((transformedIndex) => {
|
|
863
|
+
lineVisualToTransformedMap.push(segmentStart + transformedIndex);
|
|
864
|
+
});
|
|
865
|
+
lineVisualLines.push(...segmentLayout.visualLines);
|
|
866
|
+
segmentStart = i + 1;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!isExpandedLine) {
|
|
870
|
+
lineLayoutCache.set(cacheKey, {
|
|
871
|
+
visualLines: lineVisualLines,
|
|
872
|
+
logicalToVisualMap: lineLogicalToVisualMap,
|
|
873
|
+
visualToLogicalMap: lineVisualToLogicalMap,
|
|
874
|
+
transformedToLogMap,
|
|
875
|
+
visualToTransformedMap: lineVisualToTransformedMap,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
const visualLineOffset = visualLines.length;
|
|
879
|
+
visualLines.push(...lineVisualLines);
|
|
880
|
+
lineLogicalToVisualMap.forEach(([relVisualIdx, logCol]) => {
|
|
881
|
+
logicalToVisualMap[logIndex].push([
|
|
882
|
+
visualLineOffset + relVisualIdx,
|
|
883
|
+
logCol,
|
|
884
|
+
]);
|
|
885
|
+
});
|
|
886
|
+
lineVisualToLogicalMap.forEach(([, logCol]) => {
|
|
887
|
+
visualToLogicalMap.push([logIndex, logCol]);
|
|
888
|
+
});
|
|
889
|
+
transformedToLogicalMaps[logIndex] = transformedToLogMap;
|
|
890
|
+
visualToTransformedMap.push(...lineVisualToTransformedMap);
|
|
891
|
+
});
|
|
892
|
+
// If the entire logical text was empty, ensure there's one empty visual line.
|
|
893
|
+
if (logicalLines.length === 0 ||
|
|
894
|
+
(logicalLines.length === 1 && logicalLines[0] === "")) {
|
|
895
|
+
if (visualLines.length === 0) {
|
|
896
|
+
visualLines.push("");
|
|
897
|
+
if (!logicalToVisualMap[0])
|
|
898
|
+
logicalToVisualMap[0] = [];
|
|
899
|
+
logicalToVisualMap[0].push([0, 0]);
|
|
900
|
+
visualToLogicalMap.push([0, 0]);
|
|
901
|
+
visualToTransformedMap.push(0);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
visualLines,
|
|
906
|
+
logicalToVisualMap,
|
|
907
|
+
visualToLogicalMap,
|
|
908
|
+
transformedToLogicalMaps,
|
|
909
|
+
visualToTransformedMap,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
export function getTransformUnderCursor(row, col, spansByLine, options = {}) {
|
|
913
|
+
const spans = spansByLine[row];
|
|
914
|
+
if (!spans || spans.length === 0)
|
|
915
|
+
return null;
|
|
916
|
+
for (const span of spans) {
|
|
917
|
+
if (col >= span.logStart &&
|
|
918
|
+
(options.includeEdge ? col <= span.logEnd : col < span.logEnd)) {
|
|
919
|
+
return span;
|
|
920
|
+
}
|
|
921
|
+
if (col < span.logStart)
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
return null;
|
|
925
|
+
}
|
|
926
|
+
export function calculateTransformations(lines, pastedContent = {}, expandedPaste = null) {
|
|
927
|
+
return lines.map((line, index) => calculateTransformationsForLine(line, pastedContent, expandedPaste, index));
|
|
928
|
+
}
|
|
929
|
+
function textBufferReducer(state, action) {
|
|
930
|
+
const newState = bufferReducerLogic(state, action);
|
|
931
|
+
const newTransformedLines = newState.lines !== state.lines ||
|
|
932
|
+
newState.pastedContent !== state.pastedContent ||
|
|
933
|
+
newState.expandedPaste !== state.expandedPaste
|
|
934
|
+
? calculateTransformations(newState.lines, newState.pastedContent, newState.expandedPaste)
|
|
935
|
+
: state.transformationsByLine;
|
|
936
|
+
const oldTransform = getTransformUnderCursor(state.cursorRow, state.cursorCol, state.transformationsByLine);
|
|
937
|
+
const newTransform = getTransformUnderCursor(newState.cursorRow, newState.cursorCol, newTransformedLines);
|
|
938
|
+
const oldInside = oldTransform !== null;
|
|
939
|
+
const newInside = newTransform !== null;
|
|
940
|
+
const movedBetweenTransforms = oldTransform !== newTransform &&
|
|
941
|
+
(oldTransform !== null || newTransform !== null);
|
|
942
|
+
if (newState.lines !== state.lines ||
|
|
943
|
+
newState.pastedContent !== state.pastedContent ||
|
|
944
|
+
newState.expandedPaste !== state.expandedPaste ||
|
|
945
|
+
newState.viewportWidth !== state.viewportWidth ||
|
|
946
|
+
oldInside !== newInside ||
|
|
947
|
+
movedBetweenTransforms) {
|
|
948
|
+
const shouldResetPreferred = oldInside !== newInside || movedBetweenTransforms;
|
|
949
|
+
return {
|
|
950
|
+
...newState,
|
|
951
|
+
preferredCol: shouldResetPreferred ? null : newState.preferredCol,
|
|
952
|
+
visualLayout: calculateLayout(newState.lines, newState.viewportWidth, [newState.cursorRow, newState.cursorCol], newState.pastedContent, newState.expandedPaste),
|
|
953
|
+
transformationsByLine: newTransformedLines,
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
return newState;
|
|
957
|
+
}
|
|
958
|
+
export function useTextBuffer({ initialText = "", viewportWidth = 80, viewportHeight = 10, initialCursorOffset = 0, }) {
|
|
959
|
+
const initialState = useMemo(() => {
|
|
960
|
+
const lines = initialText.split("\n");
|
|
961
|
+
const [initialCursorRow, initialCursorCol] = calculateInitialCursorPosition(lines.length === 0 ? [""] : lines, initialCursorOffset);
|
|
962
|
+
const transformationsByLine = calculateTransformations(lines.length === 0 ? [""] : lines, {}, null);
|
|
963
|
+
const visualLayout = calculateLayout(lines.length === 0 ? [""] : lines, viewportWidth, [initialCursorRow, initialCursorCol], {}, null);
|
|
964
|
+
return {
|
|
965
|
+
lines: lines.length === 0 ? [""] : lines,
|
|
966
|
+
cursorRow: initialCursorRow,
|
|
967
|
+
cursorCol: initialCursorCol,
|
|
968
|
+
transformationsByLine,
|
|
969
|
+
preferredCol: null,
|
|
970
|
+
undoStack: [],
|
|
971
|
+
redoStack: [],
|
|
972
|
+
viewportWidth,
|
|
973
|
+
viewportHeight,
|
|
974
|
+
visualLayout,
|
|
975
|
+
pastedContent: {},
|
|
976
|
+
expandedPaste: null,
|
|
977
|
+
visualScrollRow: 0,
|
|
978
|
+
};
|
|
979
|
+
}, [initialText, initialCursorOffset, viewportWidth, viewportHeight]);
|
|
980
|
+
const [state, dispatch] = useReducer(textBufferReducer, initialState);
|
|
981
|
+
useEffect(() => {
|
|
982
|
+
dispatch({
|
|
983
|
+
type: "SET_VIEWPORT",
|
|
984
|
+
payload: { width: viewportWidth, height: viewportHeight },
|
|
985
|
+
});
|
|
986
|
+
}, [viewportWidth, viewportHeight]);
|
|
987
|
+
const insert = useCallback((ch, { paste = false } = {}) => {
|
|
988
|
+
if (typeof ch !== "string") {
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const textToInsert = ch;
|
|
992
|
+
let currentText = "";
|
|
993
|
+
for (const char of toCodePoints(textToInsert)) {
|
|
994
|
+
if (char.codePointAt(0) === 127) {
|
|
995
|
+
if (currentText.length > 0) {
|
|
996
|
+
dispatch({ type: "INSERT", payload: currentText, isPaste: paste });
|
|
997
|
+
currentText = "";
|
|
998
|
+
}
|
|
999
|
+
dispatch({ type: "BACKSPACE" });
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
currentText += char;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (currentText.length > 0) {
|
|
1006
|
+
dispatch({ type: "INSERT", payload: currentText, isPaste: paste });
|
|
1007
|
+
}
|
|
1008
|
+
}, []);
|
|
1009
|
+
const setText = useCallback((text, cursorPosition) => dispatch({ type: "SET_TEXT", payload: text, cursorPosition }), []);
|
|
1010
|
+
const backspace = useCallback(() => dispatch({ type: "BACKSPACE" }), []);
|
|
1011
|
+
const deleteChar = useCallback(() => dispatch({ type: "DELETE" }), []);
|
|
1012
|
+
const newline = useCallback(() => dispatch({ type: "NEWLINE" }), []);
|
|
1013
|
+
const killLineRight = useCallback(() => dispatch({ type: "KILL_LINE_RIGHT" }), []);
|
|
1014
|
+
const killLineLeft = useCallback(() => dispatch({ type: "KILL_LINE_LEFT" }), []);
|
|
1015
|
+
const deleteWordLeft = useCallback(() => dispatch({ type: "DELETE_WORD_LEFT" }), []);
|
|
1016
|
+
const deleteWordRight = useCallback(() => dispatch({ type: "DELETE_WORD_RIGHT" }), []);
|
|
1017
|
+
const move = useCallback((dir) => dispatch({ type: "MOVE", dir }), []);
|
|
1018
|
+
const undo = useCallback(() => dispatch({ type: "UNDO" }), []);
|
|
1019
|
+
const redo = useCallback(() => dispatch({ type: "REDO" }), []);
|
|
1020
|
+
const replaceRange = useCallback((startRow, startCol, endRow, endCol, text) => dispatch({
|
|
1021
|
+
type: "REPLACE_RANGE",
|
|
1022
|
+
payload: { startRow, startCol, endRow, endCol, text },
|
|
1023
|
+
}), []);
|
|
1024
|
+
const getOffset = useCallback(() => logicalPosToOffset(state.lines, state.cursorRow, state.cursorCol), [state.lines, state.cursorRow, state.cursorCol]);
|
|
1025
|
+
const togglePasteExpansion = useCallback((id, row, col) => dispatch({
|
|
1026
|
+
type: "TOGGLE_PASTE_EXPANSION",
|
|
1027
|
+
payload: { id, row, col },
|
|
1028
|
+
}), []);
|
|
1029
|
+
const handleInput = useCallback((key) => {
|
|
1030
|
+
const { sequence: input } = key;
|
|
1031
|
+
if (key.name === "backspace") {
|
|
1032
|
+
backspace();
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
if (key.name === "delete") {
|
|
1036
|
+
deleteChar();
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
if (key.name === "left") {
|
|
1040
|
+
move("left");
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
if (key.name === "right") {
|
|
1044
|
+
move("right");
|
|
1045
|
+
return true;
|
|
1046
|
+
}
|
|
1047
|
+
if (key.name === "up") {
|
|
1048
|
+
move("up");
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
if (key.name === "down") {
|
|
1052
|
+
move("down");
|
|
1053
|
+
return true;
|
|
1054
|
+
}
|
|
1055
|
+
if (key.name === "home") {
|
|
1056
|
+
move("home");
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
if (key.name === "end") {
|
|
1060
|
+
move("end");
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
if (key.name === "enter" && key.shift) {
|
|
1064
|
+
newline();
|
|
1065
|
+
return true;
|
|
1066
|
+
}
|
|
1067
|
+
if (key.name === "paste") {
|
|
1068
|
+
insert(input, { paste: true });
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
if (key.insertable) {
|
|
1072
|
+
insert(input, { paste: false });
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
}, [backspace, deleteChar, move, insert, newline]);
|
|
1077
|
+
const [vRow, vColIdx, vWidth] = useMemo(() => calculateVisualCursorFromLayout(state.visualLayout, [
|
|
1078
|
+
state.cursorRow,
|
|
1079
|
+
state.cursorCol,
|
|
1080
|
+
]), [state.visualLayout, state.cursorRow, state.cursorCol]);
|
|
1081
|
+
const visualCursor = useMemo(() => [vRow, vWidth], [vRow, vWidth]);
|
|
1082
|
+
const visualCursorColIndex = vColIdx;
|
|
1083
|
+
const viewportVisualLines = useMemo(() => state.visualLayout.visualLines.slice(state.visualScrollRow, state.visualScrollRow + state.viewportHeight), [
|
|
1084
|
+
state.visualLayout.visualLines,
|
|
1085
|
+
state.visualScrollRow,
|
|
1086
|
+
state.viewportHeight,
|
|
1087
|
+
]);
|
|
1088
|
+
return useMemo(() => ({
|
|
1089
|
+
lines: state.lines,
|
|
1090
|
+
text: state.lines.join("\n"),
|
|
1091
|
+
cursor: [state.cursorRow, state.cursorCol],
|
|
1092
|
+
preferredCol: state.preferredCol,
|
|
1093
|
+
selectionAnchor: null,
|
|
1094
|
+
pastedContent: state.pastedContent,
|
|
1095
|
+
// Visual / Viewport properties
|
|
1096
|
+
allVisualLines: state.visualLayout.visualLines,
|
|
1097
|
+
viewportVisualLines,
|
|
1098
|
+
visualCursor,
|
|
1099
|
+
visualCursorColIndex,
|
|
1100
|
+
visualCursorColWidth: vWidth,
|
|
1101
|
+
visualScrollRow: state.visualScrollRow,
|
|
1102
|
+
viewportHeight: state.viewportHeight,
|
|
1103
|
+
visualToLogicalMap: state.visualLayout.visualToLogicalMap,
|
|
1104
|
+
transformedToLogicalMaps: state.visualLayout.transformedToLogicalMaps,
|
|
1105
|
+
transformationsByLine: state.transformationsByLine,
|
|
1106
|
+
visualToTransformedMap: state.visualLayout.visualToTransformedMap,
|
|
1107
|
+
visualLayout: state.visualLayout,
|
|
1108
|
+
// Core actions
|
|
1109
|
+
insert,
|
|
1110
|
+
setText,
|
|
1111
|
+
newline,
|
|
1112
|
+
backspace,
|
|
1113
|
+
del: deleteChar,
|
|
1114
|
+
move,
|
|
1115
|
+
undo,
|
|
1116
|
+
redo,
|
|
1117
|
+
replaceRange,
|
|
1118
|
+
deleteWordLeft,
|
|
1119
|
+
deleteWordRight,
|
|
1120
|
+
killLineRight,
|
|
1121
|
+
killLineLeft,
|
|
1122
|
+
handleInput,
|
|
1123
|
+
getOffset,
|
|
1124
|
+
replaceRangeByOffset: (_s, _e, _t) => { },
|
|
1125
|
+
moveToOffset: (_offset) => { },
|
|
1126
|
+
moveToVisualPosition: (_vr, _vc) => { },
|
|
1127
|
+
getLogicalPositionFromVisual: (_vr, _vc) => null,
|
|
1128
|
+
getExpandedPasteAtLine: (lineIndex) => getExpandedPasteAtLine(lineIndex, state.expandedPaste),
|
|
1129
|
+
togglePasteExpansion,
|
|
1130
|
+
}), [
|
|
1131
|
+
state,
|
|
1132
|
+
viewportVisualLines,
|
|
1133
|
+
visualCursor,
|
|
1134
|
+
visualCursorColIndex,
|
|
1135
|
+
vWidth,
|
|
1136
|
+
insert,
|
|
1137
|
+
setText,
|
|
1138
|
+
newline,
|
|
1139
|
+
backspace,
|
|
1140
|
+
deleteChar,
|
|
1141
|
+
move,
|
|
1142
|
+
undo,
|
|
1143
|
+
redo,
|
|
1144
|
+
replaceRange,
|
|
1145
|
+
deleteWordLeft,
|
|
1146
|
+
deleteWordRight,
|
|
1147
|
+
killLineRight,
|
|
1148
|
+
killLineLeft,
|
|
1149
|
+
handleInput,
|
|
1150
|
+
getOffset,
|
|
1151
|
+
togglePasteExpansion,
|
|
1152
|
+
]);
|
|
1153
|
+
}
|
|
1154
|
+
//# sourceMappingURL=textBuffer.js.map
|