[meld] Add Previous/Next Conflict actions (closes bgo#602873)



commit dda37c052bdaf07ab7886f0efd4116a3b9d076b7
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Mon May 17 09:10:29 2010 +1000

    Add Previous/Next Conflict actions (closes bgo#602873)
    
    This patch adds two new actions that complement the existing
    chunk-skipping actions with conflict-specific previous/next actions.
    
    Differ: Add and maintain a list of chunk IDs of conflicts
    FileDiff: Implement Previous/Next Conflict actions, add a signal for
              sensitivity setting, and maintain the cursor's conflict fields
    CursorDetails: Add fields for previous and next conflict

 data/ui/filediff-ui.xml |    3 +++
 meld/diffutil.py        |    9 +++++++++
 meld/filediff.py        |   43 ++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 54 insertions(+), 1 deletions(-)
---
diff --git a/data/ui/filediff-ui.xml b/data/ui/filediff-ui.xml
index 26a83ca..cedb010 100644
--- a/data/ui/filediff-ui.xml
+++ b/data/ui/filediff-ui.xml
@@ -10,6 +10,9 @@
   <menubar name="Menubar">
     <menu action="ChangesMenu">
       <placeholder name="ChangesActions">
+        <menuitem action="PrevConflict"/>
+        <menuitem action="NextConflict"/>
+        <separator/>
         <menuitem action="PushLeft"/>
         <menuitem action="PushRight"/>
         <menuitem action="PullLeft"/>
diff --git a/meld/diffutil.py b/meld/diffutil.py
index d184e15..d8781c6 100644
--- a/meld/diffutil.py
+++ b/meld/diffutil.py
@@ -84,6 +84,7 @@ class Differ(gobject.GObject):
         self.num_sequences = 0
         self.seqlength = [0, 0, 0]
         self.diffs = [[], []]
+        self.conflicts = []
         self._merge_cache = []
         self._line_cache = [[], [], []]
         self.ignore_blanks = False
@@ -112,6 +113,14 @@ class Differ(gobject.GObject):
                 break
         self._has_mergeable_changes = (mergeable0, mergeable1)
 
+        # Conflicts can only occur when there are three panes, and will always
+        # involve the middle pane.
+        self.conflicts = []
+        for i, (c1, c2) in enumerate(self._merge_cache):
+            if (c1 is not None and c1[0] == 'conflict') or \
+               (c2 is not None and c2[0] == 'conflict'):
+                self.conflicts.append(i)
+
         self._update_line_cache()
         self.emit("diffs-changed")
 
diff --git a/meld/filediff.py b/meld/filediff.py
index bcde5de..cb43ed8 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -91,7 +91,8 @@ def insert_with_tags_by_name(buffer, line, text, tag):
     buffer.insert_with_tags_by_name(get_iter_at_line_or_eof(buffer, line), text, tag)
 
 class CursorDetails(object):
-    __slots__ = ("pane", "pos", "line", "offset", "chunk", "prev", "next")
+    __slots__ = ("pane", "pos", "line", "offset", "chunk", "prev", "next",
+                 "prev_conflict", "next_conflict")
 
     def __init__(self):
         for var in self.__slots__:
@@ -112,6 +113,10 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
     # Identifiers for MsgArea messages
     (MSG_SAME,) = range(1)
 
+    __gsignals__ = {
+        'next-conflict-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (bool, bool)),
+    }
+
     def __init__(self, prefs, num_panes):
         """Start up an filediff with num_panes empty contents.
         """
@@ -179,6 +184,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         actions = (
             ("FileOpen",          gtk.STOCK_OPEN,       None,            None, _("Open selected"), self.on_open_activate),
             ("CreatePatch",       None,                 _("Create Patch"),  None, _("Create a patch"), self.make_patch),
+            ("PrevConflict", None, _("Previous conflict"), "<Ctrl>I", _("Go to the previous conflict"), lambda x: self.on_next_conflict(gtk.gdk.SCROLL_UP)),
+            ("NextConflict", None, _("Next conflict"), "<Ctrl>K", _("Go to the next conflict"), lambda x: self.on_next_conflict(gtk.gdk.SCROLL_DOWN)),
             ("PushLeft",  gtk.STOCK_GO_BACK,    _("Push to left"),    "<Alt>Left", _("Push current change to the left"), lambda x: self.push_change(-1)),
             ("PushRight", gtk.STOCK_GO_FORWARD, _("Push to right"),   "<Alt>Right", _("Push current change to the right"), lambda x: self.push_change(1)),
             # FIXME: using LAST and FIRST is terrible and unreliable icon abuse
@@ -206,6 +213,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             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)
+        self.connect("next-conflict-changed", self.on_next_conflict_changed)
 
     def on_focus_change(self):
         self.keymask = 0
@@ -271,7 +279,22 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             if prev != self.cursor.prev or next != self.cursor.next:
                 self.emit("next-diff-changed", prev is not None,
                           next is not None)
+
+            prev_conflict, next_conflict = None, None
+            for conflict in self.linediffer.conflicts:
+                if prev is not None and conflict <= prev:
+                    prev_conflict = conflict
+                if next is not None and conflict >= next:
+                    next_conflict = conflict
+                    break
+            if prev_conflict != self.cursor.prev_conflict or \
+               next_conflict != self.cursor.next_conflict:
+                self.emit("next-conflict-changed", prev_conflict is not None,
+                          next_conflict is not None)
+
             self.cursor.prev, self.cursor.next = prev, next
+            self.cursor.prev_conflict = prev_conflict
+            self.cursor.next_conflict = next_conflict
         self.cursor.line, self.cursor.offset = line, offset
 
     def on_current_diff_changed(self, widget, *args):
@@ -313,6 +336,24 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         # FIXME: don't queue_draw() on everything... just on what changed
         self.queue_draw()
 
+    def on_next_conflict_changed(self, doc, have_prev, have_next):
+        self.actiongroup.get_action("PrevConflict").set_sensitive(have_prev)
+        self.actiongroup.get_action("NextConflict").set_sensitive(have_next)
+
+    def on_next_conflict(self, direction):
+        if direction == gtk.gdk.SCROLL_DOWN:
+            target = self.cursor.next_conflict
+        else: # direction == gtk.gdk.SCROLL_UP
+            target = self.cursor.prev_conflict
+
+        if target is None:
+            return
+
+        buf = self.textbuffer[self.cursor.pane]
+        chunk = self.linediffer.get_chunk(target, self.cursor.pane)
+        buf.place_cursor(buf.get_iter_at_line(chunk[1]))
+        self.textview[self.cursor.pane].scroll_to_mark(buf.get_insert(), 0.1)
+
     def push_change(self, direction):
         src = self._get_focused_pane()
         dst = src + direction



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