The payoff of the Command pattern: the Editor already does undo and redo for any command, without knowing what it is. Add a Delete command and it slots straight in. The editor code does not change at all (the open/closed principle in action).
Delete(pos, length): execute(doc) removes length characters at pos, remembering them so undo(doc) can splice them back inclass Document:
def __init__(self, text=""):
self.text = text
class Command:
def execute(self, doc):
raise NotImplementedError
def undo(self, doc):
raise NotImplementedError
class Insert(Command):
def __init__(self, pos, text):
self.pos = pos
self.text = text
def execute(self, doc):
doc.text = doc.text[:self.pos] + self.text + doc.text[self.pos:]
def undo(self, doc):
doc.text = doc.text[:self.pos] + doc.text[self.pos + len(self.text):]
class Delete(Command):
def __init__(self, pos, length):
self.pos = pos
self.length = length
self._removed = "" # filled in on execute, used by undo
def execute(self, doc):
"""Remove length chars at pos, remembering them for undo."""
# TODO:
# self._removed = doc.text[self.pos:self.pos + self.length]
# doc.text = doc.text[:self.pos] + doc.text[self.pos + self.length:]
pass
def undo(self, doc):
"""Splice the removed characters back in."""
# TODO: doc.text = doc.text[:self.pos] + self._removed + doc.text[self.pos:]
pass
class Editor:
def __init__(self, text=""):
self.doc = Document(text)
self._undo_stack = []
self._redo_stack = []
def do(self, command):
command.execute(self.doc)
self._undo_stack.append(command)
self._redo_stack.clear()
def undo(self):
if self._undo_stack:
command = self._undo_stack.pop()
command.undo(self.doc)
self._redo_stack.append(command)
def redo(self):
if self._redo_stack:
command = self._redo_stack.pop()
command.execute(self.doc)
self._undo_stack.append(command)
@property
def text(self):
return self.doc.text
The editor and the test runner need a wide screen. Open this page on a desktop browser and your progress will be waiting.
The payoff of the Command pattern: the Editor already does undo and redo for any command, without knowing what it is. Add a Delete command and it slots straight in. The editor code does not change at all (the open/closed principle in action).
Delete(pos, length): execute(doc) removes length characters at pos, remembering them so undo(doc) can splice them back inPress Run Tests or ββ© to check your solution.