Remove redo; undo rename on a device
Redo is gone in both file and device mode: the Edit menu entry, Ctrl/Cmd+Y and Ctrl/Cmd+Shift+Z, and the redo stack. An undone step is dropped. Undoing past the last save now leaves the saved depth unreachable, so a new edit cannot be mistaken for the saved state. Renames on a device are undoable: LiveRenameCommand renames back on the device first, and warns and clears history if the device refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Undo/redo as a stack of reversible commands.
|
||||
"""Undo as a stack of reversible commands. There is no redo: an undone step is gone.
|
||||
|
||||
Commands own the DOM mutation in both directions; in live mode they also carry it to the
|
||||
device, and raise LiveUndoError when it does not get there. Nothing here touches Tk: the view is
|
||||
Commands own the DOM mutation in both directions -- redo() is how a command is first
|
||||
applied, undo() takes it back. In live mode they also carry it to the device, and raise
|
||||
LiveUndoError when it does not get there. Nothing here touches Tk: the view is
|
||||
refreshed through a callback that main registers at startup, which also keeps the
|
||||
import graph free of cycles (sax_file and wiresheet both push commands here).
|
||||
"""
|
||||
@@ -13,13 +14,12 @@ import schema
|
||||
from sax_dom import remove_prop, restore_children, set_prop_value, update_meta_property
|
||||
|
||||
_undo_stack = []
|
||||
_redo_stack = []
|
||||
_saved_depth = 0 # len(_undo_stack) as of the last save
|
||||
_refresh_view = None
|
||||
|
||||
|
||||
def set_refresh_callback(callback):
|
||||
"""Registers how the view should be rebuilt after an undo or redo."""
|
||||
"""Registers how the view should be rebuilt after an undo."""
|
||||
global _refresh_view
|
||||
_refresh_view = callback
|
||||
|
||||
@@ -36,21 +36,20 @@ def _refresh():
|
||||
|
||||
|
||||
def push(command):
|
||||
"""Records an already-applied command. Any redo history becomes unreachable."""
|
||||
"""Records an already-applied command."""
|
||||
global _saved_depth
|
||||
# Undone past the save, the saved state can no longer be reached by undoing.
|
||||
if len(_undo_stack) < _saved_depth:
|
||||
_saved_depth = -1
|
||||
_undo_stack.append(command)
|
||||
_redo_stack.clear()
|
||||
|
||||
|
||||
def can_undo():
|
||||
return bool(_undo_stack)
|
||||
|
||||
|
||||
def can_redo():
|
||||
return bool(_redo_stack)
|
||||
|
||||
|
||||
class LiveUndoError(Exception):
|
||||
"""A live command's undo or redo did not fully reach the device.
|
||||
"""A live command's undo did not fully reach the device.
|
||||
|
||||
Raised with what did not happen; live_hooks.last_error holds why.
|
||||
"""
|
||||
@@ -65,21 +64,6 @@ def undo():
|
||||
except LiveUndoError as failure:
|
||||
_abandon_history("Undo", failure)
|
||||
return None
|
||||
_redo_stack.append(command)
|
||||
_refresh()
|
||||
return command.label
|
||||
|
||||
|
||||
def redo():
|
||||
if not _redo_stack:
|
||||
return None
|
||||
command = _redo_stack.pop()
|
||||
try:
|
||||
command.redo()
|
||||
except LiveUndoError as failure:
|
||||
_abandon_history("Redo", failure)
|
||||
return None
|
||||
_undo_stack.append(command)
|
||||
_refresh()
|
||||
return command.label
|
||||
|
||||
@@ -87,8 +71,8 @@ def redo():
|
||||
def _abandon_history(title, failure):
|
||||
"""After a step that did not reach the device, the history no longer matches it.
|
||||
|
||||
The device may hold part of the step, so neither direction can be trusted to land
|
||||
where it says. History is dropped rather than replayed against a state it never saw.
|
||||
The device may hold part of the step, so the steps below it cannot be trusted to land
|
||||
where they say. History is dropped rather than replayed against a state it never saw.
|
||||
"""
|
||||
reset()
|
||||
_refresh()
|
||||
@@ -105,14 +89,13 @@ def reset():
|
||||
"""Drops all history, for when a different file is loaded."""
|
||||
global _saved_depth
|
||||
_undo_stack.clear()
|
||||
_redo_stack.clear()
|
||||
_saved_depth = 0
|
||||
|
||||
|
||||
def reload_schema_registry():
|
||||
"""Recompiles the slot registry after the <schema> block changed.
|
||||
|
||||
A failure is swallowed: undo and redo only ever land on a state that resolved once
|
||||
A failure is swallowed: undo only ever lands on a state that resolved once
|
||||
already, so a missing manifest here means Sedona home moved underneath the session,
|
||||
which the next action reports in its own words.
|
||||
"""
|
||||
@@ -360,3 +343,19 @@ class RenameComponentCommand:
|
||||
self.element.set("name", self.new_name)
|
||||
for link, attr, _, new_value in self.link_changes:
|
||||
link.set(attr, new_value)
|
||||
|
||||
|
||||
class LiveRenameCommand(RenameComponentCommand):
|
||||
"""A rename on the device: the old name goes back there before the DOM takes it."""
|
||||
|
||||
def undo(self):
|
||||
if not live_hooks.live_rename_component(self.element, self.old_name):
|
||||
raise LiveUndoError(f"{self.new_name} was not renamed back to {self.old_name} "
|
||||
"on the device")
|
||||
super().undo()
|
||||
|
||||
def redo(self):
|
||||
if not live_hooks.live_rename_component(self.element, self.new_name):
|
||||
raise LiveUndoError(f"{self.old_name} was not renamed to {self.new_name} "
|
||||
"on the device")
|
||||
super().redo()
|
||||
|
||||
Reference in New Issue
Block a user