4.7 KiB
4.7 KiB
This is a design for a non-linear undo/redo system for kte. The design must be identical in behavior and correctness to the proven kte editor undo system.
Core Requirements
- Each open buffer has its own completely independent undo tree.
- Undo and redo must be non-linear: typing after undo creates a branch; old redo branches are discarded.
- Typing, backspacing, and pasting are batched into word-level undo steps.
- Undo/redo must never create new undo nodes while applying an undo/redo (silent, low-level apply).
- The system must be memory-safe and leak-proof even if the user types and immediately closes the buffer.
Data Structures
enum class UndoType : uint8_t {
Insert,
Delete,
Paste, // optional, can reuse Insert
Newline,
DeleteRow,
// future: IndentRegion, KillRegion, etc.
};
struct UndoNode {
UndoType type;
int row; // original cursor row
int col; // original cursor column (updated during batch)
std::string text; // the inserted or deleted text (full batch)
UndoNode* child = nullptr; // next in current timeline
UndoNode* next = nullptr; // redo branch (rarely used)
// no parent pointer needed — we walk from root
};
struct UndoTree {
UndoNode* root = nullptr; // first edit ever
UndoNode* current = nullptr; // current state of buffer
UndoNode* saved = nullptr; // points to node matching last save (for dirty flag)
UndoNode* pending = nullptr; // in-progress batch (detached)
};
Each Buffer owns one std::unique_ptr<UndoTree>.
Core API (must implement exactly)
class UndoSystem {
public:
void Begin(UndoType type);
void Append(char ch);
void Append(std::string_view text);
void commit(); // called on cursor move, commands, etc.
void undo(); // Ctrl+Z
void redo(); // Ctrl+Y or Ctrl+Shift+Z
void mark_saved(); // after successful save
void discard_pending(); // before closing buffer or loading new file
void clear(); // new file / reset
private:
void apply(const UndoNode* node, int direction); // +1 = redo, -1 = undo
void free_node(UndoNode* node);
void free_branch(UndoNode* node); // frees redo siblings only
};
Critical Invariants and Rules
-
begin()must reusependingif:- same type
- same row
pending->col + pending->text.size() == current_cursor_col→ otherwisecommit()old and create new
-
pendingis detached — never linked untilcommit() -
commit():- discards redo branches (
current->child) - attaches
pendingascurrent->child - advances
current - clears
pending - if diverged from
saved, null it
- discards redo branches (
-
apply()must use low-level buffer operations:- Never call public insert/delete/newline
- Use raw
buffer.insert_text(row, col, text)andbuffer.delete_text(row, col, len) - These must not trigger undo
-
undo():- move current to parent
- apply(current, -1)
-
redo():- move current to child
- apply(current, +1)
-
discard_pending()must be called in:- buffer close
- file reload
- new file
- any destructive operation
Example Flow: Typing "hello"
begin(Insert) → pending = new node, col=0
append('h') → pending->text = "h", pending->col = 1
append('e') → "he", col = 2
...
commit() on arrow key → pending becomes current->child, current advances
One undo step removes all of "hello".
Required Helper in Buffer Class
class Buffer {
void insert_text(int row, int col, std::string_view text); // raw, no undo
void delete_text(int row, int col, size_t len); // raw, no undo
void split_line(int row, int col); // raw newline
void join_lines(int row); // raw join
void insert_row(int row, std::string_view text); // raw
void delete_row(int row); // raw
};
Tasks for Agent
- Implement
UndoNode,UndoTree, andUndoSystemclass exactly as specified. - Add
std::unique_ptr<UndoTree> undo;toBuffer. - Modify
insert_char,delete_char,paste,newlineto useundo.begin()/append()/commit(). - Add
undo.commit()at start of all cursor movement and command functions. - Implement
apply()using onlyBuffer's raw methods. - Add
undo.discard_pending()in all buffer reset/close paths. - Add
Ctrl+Z→buffer.undo(),Ctrl+Y→buffer.redo().
This design is used in production editors and is considered the gold standard for small, correct, non-linear undo in C/C++. Implement it faithfully.