Textualize/textual · error · HistoryException
Cannot add an edit to history before it has been performed u
Error message
Cannot add an edit to history before it has been performed using `Edit.do`.
What it means
History.record raises HistoryException when asked to record an Edit that has not been performed yet — the edit carries no _edit_result because Edit.do was never called. History only accepts edits that actually happened, so it can store the replaced text for undo.
Source
Thrown at src/textual/document/_history.py:78
- The checkpoint timer expires.
- The maximum number of characters permitted in a checkpoint is reached.
- A redo is performed (we should not add new edits to a batch that has been redone).
- The programmer has requested a new batch via a call to `force_new_batch`.
- e.g. the TextArea widget may call this method in some circumstances.
- Clicking to move the cursor elsewhere in the document should create a new batch.
- Movement of the cursor via a keyboard action that is NOT an edit.
- Blurring the TextArea creates a new checkpoint.
- The current edit involves a deletion/replacement and the previous edit did not.
- The current edit is a pure insertion and the previous edit was not.
- The edit involves insertion or deletion of one or more newline characters.
- An edit which inserts more than a single character (a paste) gets an isolated batch.
Args:
edit: The edit to record.
"""
edit_result = edit._edit_result
if edit_result is None:
raise HistoryException(
"Cannot add an edit to history before it has been performed using `Edit.do`."
)
if edit.text == "" and edit_result.replaced_text == "":
return None
is_replacement = bool(edit_result.replaced_text)
undo_stack = self._undo_stack
current_time = self._get_time()
edit_characters = len(edit.text)
contains_newline = "\n" in edit.text or "\n" in edit_result.replaced_text
# Determine whether to create a new batch, or add to the latest batch.
if (
not undo_stack
or self._force_end_batch
or edit_characters > 1
or contains_newlineView on GitHub (pinned to 06dbeef4bb)
Solutions
- Call edit.do(document) before history.record(edit)
- Or use the higher-level Document.edit API, which performs and records atomically
- Skip recording for no-op edits (record already ignores empty edits after do)
Example fix
# before edit = Edit(from_loc, to_loc, text) history.record(edit) # raises # after edit = Edit(from_loc, to_loc, text) edit.do(document) history.record(edit)
Defensive patterns
Strategy: validation
Validate before calling
def perform_and_record(history, edit, document):
if edit._edit_result is None:
edit.do(document)
history.record(edit) Try / catch
from textual.document._history import HistoryException
try:
history.record(edit)
except HistoryException:
edit.do(document)
history.record(edit) Prevention
- Always call edit.do() before history.record()
- Prefer Document.edit() which does both atomically
- Skip recording for no-op edits
When it happens
Trigger: Creating an Edit(edit) and passing it to history.record(edit) directly without first calling edit.do(document). Typically hit when writing custom edit flows in TextArea/document code.
Common situations: Custom undo/redo integrations or scripted transformations where the developer builds the edit and records it manually, forgetting the do step.
Related errors
- Index {index!r} does not correspond to a location in the doc
- The document line index {line_index!r} is out of bounds. The
- Can't animate attribute {attribute!r} on {obj!r}; attribute
- Don't know how to animate {value!r}; Can only animate <int>,
- Can't encode {datum!r}
AI-assisted analysis of Textualize/textual@06dbeef4bb (2026-08-27).
Data as JSON: /api/errors/d497aec378624ef8.
Report an issue: GitHub.