fix(tui): keep paste registry in sync when deleting paste markers
Undo snapshots now restore paste content and counters alongside editor text. Paste marker renumbering shifts registry entries in ascending ID order before rewriting markers, preventing literal or incorrect paste content on submit.\n\nCloses #6844
This commit is contained in:
@@ -212,6 +212,13 @@ interface EditorState {
|
||||
cursorCol: number;
|
||||
}
|
||||
|
||||
/** Undo snapshot: editor text state plus the paste registry. */
|
||||
interface EditorSnapshot {
|
||||
state: EditorState;
|
||||
pastes: Map<number, string>;
|
||||
pasteCounter: number;
|
||||
}
|
||||
|
||||
interface LayoutLine {
|
||||
text: string;
|
||||
hasCursor: boolean;
|
||||
@@ -318,7 +325,7 @@ export class Editor implements Component, Focusable {
|
||||
private snappedFromCursorCol: number | null = null;
|
||||
|
||||
// Undo support
|
||||
private undoStack = new UndoStack<EditorState>();
|
||||
private undoStack = new UndoStack<EditorSnapshot>();
|
||||
|
||||
public onSubmit?: (text: string) => void;
|
||||
public onChange?: (text: string) => void;
|
||||
@@ -999,13 +1006,13 @@ export class Editor implements Component, Focusable {
|
||||
this.cancelAutocomplete();
|
||||
this.lastAction = null;
|
||||
this.exitHistoryBrowsing();
|
||||
this.pastes.clear();
|
||||
this.pasteCounter = 0;
|
||||
const normalized = this.normalizeText(text);
|
||||
// Push undo snapshot if content differs (makes programmatic changes undoable)
|
||||
if (this.getText() !== normalized) {
|
||||
this.pushUndoSnapshot();
|
||||
}
|
||||
this.pastes.clear();
|
||||
this.pasteCounter = 0;
|
||||
this.setTextInternal(normalized);
|
||||
}
|
||||
|
||||
@@ -1284,17 +1291,21 @@ export class Editor implements Component, Focusable {
|
||||
this.pastes.delete(targetId);
|
||||
this.pasteCounter--;
|
||||
|
||||
// We got to update id of markers which are greater than the removed one
|
||||
// Shift registry entries down in ascending id order, independent
|
||||
// of marker order in the text ([paste #3] becomes [paste #2] when
|
||||
// [paste #1] is removed).
|
||||
const higherIds = [...this.pastes.keys()].filter((id) => id > targetId).sort((a, b) => a - b);
|
||||
for (const id of higherIds) {
|
||||
this.pastes.set(id - 1, this.pastes.get(id)!);
|
||||
this.pastes.delete(id);
|
||||
}
|
||||
|
||||
// Renumber markers with ids greater than the removed one.
|
||||
this.state.lines = this.state.lines.map((line) =>
|
||||
line.replace(PASTE_MARKER_REGEX, (fullMatch, idGroup, suffixGroup) => {
|
||||
const x = Number(idGroup);
|
||||
if (x <= targetId) return fullMatch;
|
||||
|
||||
// [paste #3] become [paste #2] if we remove [paste #1]
|
||||
const newText = `[paste #${x - 1}${suffixGroup}]`;
|
||||
this.pastes.set(x - 1, this.pastes.get(x) ?? newText);
|
||||
this.pastes.delete(x);
|
||||
return newText;
|
||||
return `[paste #${x - 1}${suffixGroup}]`;
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1994,14 +2005,16 @@ export class Editor implements Component, Focusable {
|
||||
}
|
||||
|
||||
private pushUndoSnapshot(): void {
|
||||
this.undoStack.push(this.state);
|
||||
this.undoStack.push({ state: this.state, pastes: this.pastes, pasteCounter: this.pasteCounter });
|
||||
}
|
||||
|
||||
private undo(): void {
|
||||
this.exitHistoryBrowsing();
|
||||
const snapshot = this.undoStack.pop();
|
||||
if (!snapshot) return;
|
||||
Object.assign(this.state, snapshot);
|
||||
Object.assign(this.state, snapshot.state);
|
||||
this.pastes = snapshot.pastes;
|
||||
this.pasteCounter = snapshot.pasteCounter;
|
||||
this.lastAction = null;
|
||||
this.preferredVisualCol = null;
|
||||
if (this.onChange) {
|
||||
|
||||
Reference in New Issue
Block a user