[meld] Rework undo behaviour; support undo/redo after save (closes bgo#156984)



commit d3265e173cf7ba12bb93a9de1ff2c81202004915
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Wed Mar 17 12:13:21 2010 +1000

    Rework undo behaviour; support undo/redo after save (closes bgo#156984)
    
    The current undo stack implementation clears all undo information on
    save, because its modification tracking doesn't allow for easily
    repositioning the modification point within the stack. This commit adds
    per-buffer tracking of modification points within the stack, and
    repositions these as appropriate on save. In addition, we now emit
    signals when the modified state of a buffer (as defined by the undo
    stack) changes.

 meld/filediff.py |   35 ++++++++---------------------------
 meld/undo.py     |   44 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 51 insertions(+), 28 deletions(-)
---
diff --git a/meld/filediff.py b/meld/filediff.py
index f8a5816..8989a62 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -203,6 +203,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             t.connect("focus-in-event", self.on_current_diff_changed)
             t.connect("focus-out-event", self.on_current_diff_changed)
         self.linediffer.connect("diffs-changed", self.on_diffs_changed)
+        self.undosequence.connect("checkpointed", self.on_undo_checkpointed)
 
     def on_focus_change(self):
         self.keymask = 0
@@ -568,12 +569,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
 
     def on_text_insert_text(self, buffer, it, text, textlen):
         if not self.undosequence_busy:
-            self.undosequence.begin_group()
-            pane = self.textbuffer.index(buffer)
-            if self.bufferdata[pane].modified != 1:
-                self.undosequence.add_action( BufferModifiedAction(buffer, self) )
             self.undosequence.add_action( BufferInsertionAction(buffer, it.get_offset(), text) )
-            self.undosequence.end_group()
 
     def on_text_delete_range(self, buffer, it0, it1):
         text = buffer.get_text(it0, it1, 0)
@@ -581,11 +577,10 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         assert self.deleted_lines_pending == -1
         self.deleted_lines_pending = text.count("\n")
         if not self.undosequence_busy:
-            self.undosequence.begin_group()
-            if self.bufferdata[pane].modified != 1:
-                self.undosequence.add_action( BufferModifiedAction(buffer, self) )
             self.undosequence.add_action( BufferDeletionAction(buffer, it0.get_offset(), text) )
-            self.undosequence.end_group()
+
+    def on_undo_checkpointed(self, undosequence, buf, checkpointed):
+        self.set_buffer_modified(buf, not checkpointed)
 
         #
         #
@@ -706,6 +701,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         self.scheduler.add_task( self._set_files_internal(files).next )
 
     def _load_files(self, files, textbuffers, panetext):
+        self.undosequence.clear()
         yield _("[%s] Set num panes") % self.label_text
         self.set_num_panes( len(files) )
         self._disconnect_buffer_handlers()
@@ -786,9 +782,10 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                             t.text.append("\n")
                         panetext[t.pane] = "".join(t.text)
             yield 1
+        for b in self.textbuffer:
+            self.undosequence.checkpoint(b)
 
     def _diff_files(self, files, panetext):
-        self.undosequence.clear()
         yield _("[%s] Computing differences") % self.label_text
         panetext = [self._filter_text(p) for p in panetext]
         lines = map(lambda x: x.split("\n"), panetext)
@@ -1029,8 +1026,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                     return melddoc.RESULT_ERROR
         if self._save_text_to_filename(bufdata.filename, text):
             self.emit("file-changed", bufdata.filename)
-            self.undosequence.clear()
-            self.set_buffer_modified(buf, 0)
+            self.undosequence.checkpoint(buf)
             return melddoc.RESULT_OK
         else:
             return melddoc.RESULT_ERROR
@@ -1498,18 +1494,3 @@ class BufferDeletionAction(BufferAction):
         super(BufferDeletionAction, self).__init__(buf, offset, text)
         self.undo = self.insert
         self.redo = self.delete
-
-################################################################################
-#
-# BufferModifiedAction
-#
-################################################################################
-class BufferModifiedAction(object):
-    """A helper to set modified flag on a text buffer"""
-    def __init__(self, buf, app):
-        self.buffer, self.app = buf, app
-        self.app.set_buffer_modified(self.buffer, 1)
-    def undo(self):
-        self.app.set_buffer_modified(self.buffer, 0)
-    def redo(self):
-        self.app.set_buffer_modified(self.buffer, 1)
diff --git a/meld/undo.py b/meld/undo.py
index c0dac55..ad50993 100644
--- a/meld/undo.py
+++ b/meld/undo.py
@@ -51,7 +51,8 @@ class UndoSequence(gobject.GObject):
 
     __gsignals__ = {
         'can-undo': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)),
-        'can-redo': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,))
+        'can-redo': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_BOOLEAN,)),
+        'checkpointed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT, gobject.TYPE_BOOLEAN,)),
     }
 
     def __init__(self):
@@ -60,6 +61,7 @@ class UndoSequence(gobject.GObject):
         gobject.GObject.__init__(self)
         self.actions = []
         self.next_redo = 0
+        self.checkpoints = {}
         self.group = None
 
     def clear(self):
@@ -79,6 +81,7 @@ class UndoSequence(gobject.GObject):
             self.emit('can-redo', 0)
         self.actions = []
         self.next_redo = 0
+        self.checkpoints = {}
         self.group = None
 
     def can_undo(self):
@@ -100,6 +103,15 @@ class UndoSequence(gobject.GObject):
                   which are called by this sequence during an undo or redo.
         """
         if self.group is None:
+            if self.checkpointed(action.buffer):
+                self.checkpoints[action.buffer][1] = self.next_redo
+                self.emit('checkpointed', action.buffer, False)
+            else:
+                # If we go back in the undo stack before the checkpoint starts,
+                # and then modify the buffer, we lose the checkpoint altogether
+                start, end = self.checkpoints.get(action.buffer, (None, None))
+                if start is not None and start > self.next_redo:
+                    self.checkpoints[action.buffer] = (None, None)
             could_undo = self.can_undo()
             could_redo = self.can_redo()
             self.actions[self.next_redo:] = []
@@ -118,6 +130,9 @@ class UndoSequence(gobject.GObject):
         Raises an AssertionError if the sequence is not undoable.
         """
         assert self.next_redo > 0
+        buf = self.actions[self.next_redo - 1].buffer
+        if self.checkpointed(buf):
+            self.emit('checkpointed', buf, False)
         could_redo = self.can_redo()
         self.next_redo -= 1
         self.actions[self.next_redo].undo()
@@ -125,6 +140,8 @@ class UndoSequence(gobject.GObject):
             self.emit('can-undo', 0)
         if not could_redo:
             self.emit('can-redo', 1)
+        if self.checkpointed(buf):
+            self.emit('checkpointed', buf, True)
 
     def redo(self):
         """Redo an action.
@@ -132,6 +149,9 @@ class UndoSequence(gobject.GObject):
         Raises and AssertionError if the sequence is not undoable.
         """
         assert self.next_redo < len(self.actions)
+        buf = self.actions[self.next_redo].buffer
+        if self.checkpointed(buf):
+            self.emit('checkpointed', buf, False)
         could_undo = self.can_undo()
         a = self.actions[self.next_redo]
         self.next_redo += 1
@@ -140,6 +160,28 @@ class UndoSequence(gobject.GObject):
             self.emit('can-undo', 1)
         if not self.can_redo():
             self.emit('can-redo', 0)
+        if self.checkpointed(buf):
+            self.emit('checkpointed', buf, True)
+
+    def checkpoint(self, buf):
+        start = self.next_redo
+        while start > 0 and self.actions[start - 1].buffer != buf:
+            start -= 1
+        end = self.next_redo
+        while end < len(self.actions) and self.actions[end + 1].buffer != buf:
+            end += 1
+        if end == len(self.actions):
+            end = None
+        self.checkpoints[buf] = [start, end]
+        self.emit('checkpointed', buf, True)
+
+    def checkpointed(self, buf):
+        # While the main undo sequence should always have checkpoints
+        # recorded, grouped subsequences won't.
+        start, end = self.checkpoints.get(buf, (None, None))
+        if start is None:
+            return False
+        return start <= self.next_redo <= (end or len(self.actions))
 
     def begin_group(self):
         """Group several actions into a single logical action.



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]