[meld] Fix change categorisation around synchronisation points (bgo#699399)



commit a7150766d8d7c8a9b87f4fa03c9ee0a2dfb9ddc6
Author: Kai Willadsen <kai willadsen gmail com>
Date:   Sun Jun 30 08:08:24 2013 +1000

    Fix change categorisation around synchronisation points (bgo#699399)
    
    Previously we did our synchronisation point split, but when handing off
    the result to diffutil we removed the split. This resulted in weird
    situations where what should have been paired inserts were merged into
    a single changed block.
    
    This change makes it so that we keep an internal copy of our split
    results and use that when getting the opcodes.
    
    This also half-fixes bgo#699067 by changing to use gtk.TextMarks for
    sync points, but editing still breaks things.

 meld/diffutil.py |    3 ++-
 meld/filediff.py |   26 +++++++++++++++++++++-----
 meld/matchers.py |   45 +++++++++++++++++++++++++++++++++++++++------
 3 files changed, 62 insertions(+), 12 deletions(-)
---
diff --git a/meld/diffutil.py b/meld/diffutil.py
index 446566e..d1b7106 100644
--- a/meld/diffutil.py
+++ b/meld/diffutil.py
@@ -458,9 +458,10 @@ class Differ(gobject.GObject):
 
         for i in range(self.num_sequences - 1):
             if self.syncpoints:
+                syncpoints = [(s[0](), s[1]()) for s in self.syncpoints[i]]
                 matcher = self._sync_matcher(None,
                                              sequences[1], sequences[i * 2],
-                                             syncpoints=self.syncpoints[i])
+                                             syncpoints=syncpoints)
             else:
                 matcher = self._matcher(None, sequences[1], sequences[i * 2])
             work = matcher.initialise()
diff --git a/meld/filediff.py b/meld/filediff.py
index 5ffa688..55f3677 100644
--- a/meld/filediff.py
+++ b/meld/filediff.py
@@ -1471,8 +1471,11 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             context.restore()
 
         for syncpoint in [p[pane] for p in self.syncpoints]:
-            if bounds[0] <= syncpoint <= bounds[1]:
-                ypos = textview.get_y_for_line_num(syncpoint) - visible.y
+            if not syncpoint:
+                continue
+            syncline = textbuffer.get_iter_at_mark(syncpoint).get_line()
+            if bounds[0] <= syncline <= bounds[1]:
+                ypos = textview.get_y_for_line_num(syncline) - visible.y
                 context.rectangle(-0.5, ypos - 0.5, width + 1, 1)
                 context.set_source_color(self.syncpoint_color)
                 context.stroke()
@@ -1936,16 +1939,29 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             syncpoint = [None] * self.num_panes
         cursor_it = self.textbuffer[pane].get_iter_at_mark(
             self.textbuffer[pane].get_insert())
-        syncpoint[pane] = cursor_it.get_line()
+        syncpoint[pane] = self.textbuffer[pane].create_mark(None, cursor_it)
         self.syncpoints.append(syncpoint)
 
+        def make_line_retriever(pane, marks):
+            buf = self.textbuffer[pane]
+            mark = marks[pane]
+
+            def get_line_for_mark():
+                return buf.get_iter_at_mark(mark).get_line()
+            return get_line_for_mark
+
         valid_points = [p for p in self.syncpoints if all(p)]
         if valid_points and self.num_panes == 2:
             self.linediffer.syncpoints = [
-                ((p[1], p[0]), ) for p in valid_points]
+                ((make_line_retriever(1, p), make_line_retriever(0, p)), )
+                for p in valid_points
+            ]
         elif valid_points and self.num_panes == 3:
             self.linediffer.syncpoints = [
-                ((p[1], p[0]), (p[1], p[2])) for p in valid_points]
+                ((make_line_retriever(1, p), make_line_retriever(0, p)),
+                 (make_line_retriever(1, p), make_line_retriever(2, p)))
+                for p in valid_points
+            ]
         self.refresh_comparison()
 
     def clear_sync_points(self, action):
diff --git a/meld/matchers.py b/meld/matchers.py
index 8b9fa27..2b75dc1 100644
--- a/meld/matchers.py
+++ b/meld/matchers.py
@@ -383,22 +383,55 @@ class SyncPointMyersSequenceMatcher(MyersSequenceMatcher):
             if ai < len(self.a) or bi < len(self.b):
                 chunks.append((ai, bi, self.a[ai:], self.b[bi:]))
 
+            self.split_matching_blocks = []
             self.matching_blocks = []
             for ai, bi, a, b in chunks:
+                matching_blocks = []
                 matcher = MyersSequenceMatcher(self.isjunk, a, b)
                 for i in matcher.initialise():
                     yield None
                 blocks = matcher.get_matching_blocks()
-                l = len(self.matching_blocks) - 1
+                l = len(matching_blocks) - 1
                 if l >= 0 and len(blocks) > 1:
-                    aj = self.matching_blocks[l][0]
-                    bj = self.matching_blocks[l][1]
-                    bl = self.matching_blocks[l][2]
+                    aj = matching_blocks[l][0]
+                    bj = matching_blocks[l][1]
+                    bl = matching_blocks[l][2]
                     if (aj + bl == ai and bj + bl == bi and
                             blocks[0][0] == 0 and blocks[0][1] == 0):
                         block = blocks.pop(0)
-                        self.matching_blocks[l] = (aj, bj, bl + block[2])
+                        matching_blocks[l] = (aj, bj, bl + block[2])
                 for x, y, l in blocks[:-1]:
-                    self.matching_blocks.append((ai + x, bi + y, l))
+                    matching_blocks.append((ai + x, bi + y, l))
+                self.matching_blocks.extend(matching_blocks)
+                # Split matching blocks each need to be terminated to get our
+                # split chunks correctly created
+                self.split_matching_blocks.append(
+                    matching_blocks + [(ai + len(a), bi + len(b), 0)])
             self.matching_blocks.append((len(self.a), len(self.b), 0))
             yield 1
+
+    def get_opcodes(self):
+        # This is just difflib.SequenceMatcher.get_opcodes in which we instead
+        # iterate over our internal set of split matching blocks.
+        if self.opcodes is not None:
+            return self.opcodes
+        i = j = 0
+        self.opcodes = opcodes = []
+        self.get_matching_blocks()
+        for matching_blocks in self.split_matching_blocks:
+            for ai, bj, size in matching_blocks:
+                tag = ''
+                if i < ai and j < bj:
+                    tag = 'replace'
+                elif i < ai:
+                    tag = 'delete'
+                elif j < bj:
+                    tag = 'insert'
+                if tag:
+                    opcodes.append((tag, i, ai, j, bj))
+                i, j = ai+size, bj+size
+                # the list of matching blocks is terminated by a
+                # sentinel with size 0
+                if size:
+                    opcodes.append(('equal', ai, i, bj, j))
+        return [DiffChunk._make(chunk) for chunk in opcodes]


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