Fix characters being dropped in text-buffer (#2504)

Co-authored-by: Sandy Tao <sandytao520@icloud.com>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Billy Biggs 2025-07-01 19:07:41 -04:00 committed by GitHub
parent 82afc75350
commit 3a995305c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 98 additions and 134 deletions

View File

@ -420,6 +420,7 @@ export function useTextBuffer({
const [undoStack, setUndoStack] = useState<UndoHistoryEntry[]>([]); const [undoStack, setUndoStack] = useState<UndoHistoryEntry[]>([]);
const [redoStack, setRedoStack] = useState<UndoHistoryEntry[]>([]); const [redoStack, setRedoStack] = useState<UndoHistoryEntry[]>([]);
const historyLimit = 100; const historyLimit = 100;
const [opQueue, setOpQueue] = useState<UpdateOperation[]>([]);
const [clipboard, setClipboard] = useState<string | null>(null); const [clipboard, setClipboard] = useState<string | null>(null);
const [selectionAnchor, setSelectionAnchor] = useState< const [selectionAnchor, setSelectionAnchor] = useState<
@ -526,51 +527,16 @@ export function useTextBuffer({
return _restoreState(state); return _restoreState(state);
}, [redoStack, lines, cursorRow, cursorCol, _restoreState]); }, [redoStack, lines, cursorRow, cursorCol, _restoreState]);
const insertStr = useCallback( const applyOperations = useCallback((ops: UpdateOperation[]) => {
(str: string): boolean => {
dbg('insertStr', { str, beforeCursor: [cursorRow, cursorCol] });
if (str === '') return false;
pushUndo();
let normalised = str.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
normalised = stripUnsafeCharacters(normalised);
const parts = normalised.split('\n');
const newLines = [...lines];
const lineContent = currentLine(cursorRow);
const before = cpSlice(lineContent, 0, cursorCol);
const after = cpSlice(lineContent, cursorCol);
newLines[cursorRow] = before + parts[0];
if (parts.length > 1) {
// Adjusted condition for inserting multiple lines
const remainingParts = parts.slice(1);
const lastPartOriginal = remainingParts.pop() ?? '';
newLines.splice(cursorRow + 1, 0, ...remainingParts);
newLines.splice(
cursorRow + parts.length - 1,
0,
lastPartOriginal + after,
);
setCursorRow(cursorRow + parts.length - 1);
setCursorCol(cpLen(lastPartOriginal));
} else {
setCursorCol(cpLen(before) + cpLen(parts[0]));
}
setLines(newLines);
setPreferredCol(null);
return true;
},
[pushUndo, cursorRow, cursorCol, lines, currentLine, setPreferredCol],
);
const applyOperations = useCallback(
(ops: UpdateOperation[]) => {
if (ops.length === 0) return; if (ops.length === 0) return;
setOpQueue((prev) => [...prev, ...ops]);
}, []);
useEffect(() => {
if (opQueue.length === 0) return;
const expandedOps: UpdateOperation[] = []; const expandedOps: UpdateOperation[] = [];
for (const op of ops) { for (const op of opQueue) {
if (op.type === 'insert') { if (op.type === 'insert') {
let currentText = ''; let currentText = '';
for (const char of toCodePoints(op.payload)) { for (const char of toCodePoints(op.payload)) {
@ -594,6 +560,7 @@ export function useTextBuffer({
} }
if (expandedOps.length === 0) { if (expandedOps.length === 0) {
setOpQueue([]); // Clear queue even if ops were no-ops
return; return;
} }
@ -645,8 +612,7 @@ export function useTextBuffer({
const prevLineContent = currentLine(newCursorRow - 1); const prevLineContent = currentLine(newCursorRow - 1);
const currentLineContentVal = currentLine(newCursorRow); const currentLineContentVal = currentLine(newCursorRow);
const newCol = cpLen(prevLineContent); const newCol = cpLen(prevLineContent);
newLines[newCursorRow - 1] = newLines[newCursorRow - 1] = prevLineContent + currentLineContentVal;
prevLineContent + currentLineContentVal;
newLines.splice(newCursorRow, 1); newLines.splice(newCursorRow, 1);
newCursorRow--; newCursorRow--;
newCursorCol = newCol; newCursorCol = newCol;
@ -658,16 +624,13 @@ export function useTextBuffer({
setCursorRow(newCursorRow); setCursorRow(newCursorRow);
setCursorCol(newCursorCol); setCursorCol(newCursorCol);
setPreferredCol(null); setPreferredCol(null);
},
[lines, cursorRow, cursorCol, pushUndo, setPreferredCol], // Clear the queue after processing
); setOpQueue((prev) => prev.slice(opQueue.length));
}, [opQueue, lines, cursorRow, cursorCol, pushUndo, setPreferredCol]);
const insert = useCallback( const insert = useCallback(
(ch: string): void => { (ch: string): void => {
if (/[\n\r]/.test(ch)) {
insertStr(ch);
return;
}
dbg('insert', { ch, beforeCursor: [cursorRow, cursorCol] }); dbg('insert', { ch, beforeCursor: [cursorRow, cursorCol] });
ch = stripUnsafeCharacters(ch); ch = stripUnsafeCharacters(ch);
@ -694,7 +657,7 @@ export function useTextBuffer({
} }
applyOperations([{ type: 'insert', payload: ch }]); applyOperations([{ type: 'insert', payload: ch }]);
}, },
[applyOperations, cursorRow, cursorCol, isValidPath, insertStr], [applyOperations, cursorRow, cursorCol, isValidPath],
); );
const newline = useCallback((): void => { const newline = useCallback((): void => {
@ -1397,8 +1360,9 @@ export function useTextBuffer({
}, [selectionAnchor, cursorRow, cursorCol, currentLine, setClipboard]), }, [selectionAnchor, cursorRow, cursorCol, currentLine, setClipboard]),
paste: useCallback(() => { paste: useCallback(() => {
if (clipboard === null) return false; if (clipboard === null) return false;
return insertStr(clipboard); applyOperations([{ type: 'insert', payload: clipboard }]);
}, [clipboard, insertStr]), return true;
}, [clipboard, applyOperations]),
startSelection: useCallback( startSelection: useCallback(
() => setSelectionAnchor([cursorRow, cursorCol]), () => setSelectionAnchor([cursorRow, cursorCol]),
[cursorRow, cursorCol, setSelectionAnchor], [cursorRow, cursorCol, setSelectionAnchor],