Index: diffutil.py
===================================================================
--- diffutil.py	(revision 1329)
+++ diffutil.py	(working copy)
@@ -76,6 +76,7 @@
         else: # sequence==1:
             changes[0] = self._change_sequence(         0, sequence, startidx, sizechange, texts)
             if self.num_sequences == 3:
+                self.seqlength[sequence] -= sizechange
                 changes[1] = self._change_sequence(     1, sequence, startidx, sizechange, texts)
         return changes
 
@@ -90,6 +91,9 @@
                 i += 1
         return i
 
+    def _get_matcher(self, a, b):
+        return IncrementalSequenceMatcher(None, a, b)
+
     def _change_sequence(self, which, sequence, startidx, sizechange, texts):
         diffs = self.diffs[which]
         lines_added = [0,0,0]
@@ -118,7 +122,7 @@
         linesx = texts[x][rangex[0]:rangex[1]]
         lines1 = texts[1][range1[0]:range1[1]]
         #print "<<<\n%s\n===\n%s\n>>>" % ("\n".join(linesx),"\n".join(lines1))
-        newdiffs = IncrementalSequenceMatcher( None, lines1, linesx).get_difference_opcodes()
+        newdiffs = self._get_matcher( lines1, linesx).get_difference_opcodes()
         newdiffs = [ (c[0], c[1]+range1[0],c[2]+range1[0], c[3]+rangex[0],c[4]+rangex[0]) for c in newdiffs]
         if hiidx < len(self.diffs[which]):
             self.diffs[which][hiidx:] = [ (c[0],
@@ -172,25 +176,17 @@
                     c = cs[1]
                     yield c[0], c[1], c[2], c[3], c[4], 2
 
-    def _merge_blocks(self, using, low_seq, high_seq, last_diff):
+    def _merge_blocks(self, using):
         LO, HI = 1,2
-        lowc  = using[low_seq][ 0][LO]
-        highc = using[low_seq][-1][HI]
-        if len(using[not low_seq]):
-            lowc  =  min(lowc,  using[not low_seq][ 0][LO])
-            highc =  max(highc, using[not low_seq][-1][HI])
+        lowc  =  min(using[0][ 0][LO], using[1][ 0][LO])
+        highc =  max(using[0][-1][HI], using[1][-1][HI])
         low = []
         high = []
         for i in (0,1):
-            if len(using[i]):
-                d = using[i][0]
-                low.append(  lowc  - d[LO] + d[2+LO] )
-                d = using[i][-1]
-                high.append( highc - d[HI] + d[2+HI] )
-            else:
-                d = last_diff
-                low.append(  lowc  - d[LO] + d[2+LO] )
-                high.append( highc - d[HI] + d[2+HI] )
+            d = using[i][0]
+            low.append(  lowc  - d[LO] + d[2+LO] )
+            d = using[i][-1]
+            high.append( highc - d[HI] + d[2+HI] )
         return low[0], high[0], lowc, highc, low[1], high[1]
 
     def _merge_diffs(self, seq0, seq1, texts):
@@ -205,6 +201,11 @@
                 base_seq = 0
             else:
                 base_seq = seq0[0][LO] > seq1[0][LO]
+                if seq0[0][LO] == seq1[0][LO]:
+                    if seq0[0][0] == 'insert':
+                        base_seq = 0
+                    elif seq1[0][0] == 'insert':
+                        base_seq = 1
 
             high_seq = base_seq
             high_diff = seq[high_seq].pop(0)
@@ -222,6 +223,8 @@
                 else:
                     if high_mark < other_diff[LO]:
                         break
+                    if high_mark == other_diff[LO] and (high_diff[0] != 'insert' or other_diff[0] != 'insert'):
+                        break
 
                 using[other_seq].append(other_diff)
                 seq[other_seq].pop(0)
@@ -229,10 +232,8 @@
                 if high_mark < other_diff[HI]:
                     high_seq ^= 1
                     high_diff = other_diff
-                    high_mark = other_diff[HI]
+                    high_mark = high_diff[HI]
 
-            block = self._merge_blocks( using, base_seq, high_seq, block)
-
             if len(using[0])==0:
                 assert len(using[1])==1
                 yield None, using[1][0]
@@ -240,9 +241,13 @@
                 assert len(using[0])==1
                 yield using[0][0], None
             else:
+                block = self._merge_blocks( using )
                 l0, h0, l1, h1, l2, h2 = block
                 if h0-l0 == h2-l2 and texts[0][l0:h0] == texts[2][l2:h2]:
-                    if l1 != h1:
+                    if l1 != h1 and l0 == h0:
+                        out0 = ('delete', block[2], block[3], block[0], block[1])
+                        out1 = ('delete', block[2], block[3], block[4], block[5])
+                    elif l1 != h1:
                         out0 = ('replace', block[2], block[3], block[0], block[1])
                         out1 = ('replace', block[2], block[3], block[4], block[5])
                     else:
@@ -260,7 +265,7 @@
         self.seqlength = [len(s) for s in sequences]
 
         for i in range(self.num_sequences - 1):
-            matcher = IncrementalSequenceMatcher(None, sequences[1], sequences[i*2])
+            matcher = self._get_matcher( sequences[1], sequences[i*2])
             work = matcher.initialise()
             while work.next() == None:
                 yield None
Index: merge.py
===================================================================
--- merge.py	(revision 0)
+++ merge.py	(revision 0)
@@ -0,0 +1,216 @@
+### Copyright (C) 2009 Piotr Piastucki <the_leech@users.berlios.de>
+
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software
+### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import gtk
+import diffutil
+import matchers
+
+class Merger(diffutil.Differ):
+    def __init__(self):
+        diffutil.Differ.__init__(self)
+
+    def _get_matcher(self, a, b):
+        return matchers.MyersMatcher(a, b)
+        #return matchers.LCSMatcher(1, a, b)
+        #return diffutil.IncrementalSequenceMatcher(None, a, b)
+
+    def _merge_diffs(self, seq0, seq1, texts):
+        for c in self._resolve_conflicts(seq0, seq1, texts):
+            yield c
+
+    def _resolve_conflicts(self, seq0, seq1, texts):
+        seq0, seq1 = seq0[:], seq1[:]
+        seq = seq0, seq1
+        LO, HI = 1,2
+        while len(seq0) or len(seq1):
+            if len(seq0) == 0:
+                base_seq = 1
+            elif len(seq1) == 0:
+                base_seq = 0
+            else:
+                base_seq = seq0[0][LO] > seq1[0][LO]
+                if seq0[0][LO] == seq1[0][LO]:
+                    if seq0[0][0] == 'insert':
+                        base_seq = 0
+                    elif seq1[0][0] == 'insert':
+                        base_seq = 1
+
+
+            high_seq = base_seq
+            high_diff = seq[high_seq].pop(0)
+            high_mark = high_diff[HI]
+
+            using = [[], []]
+            using[high_seq].append(high_diff)
+
+            while 1:
+                other_seq = high_seq ^ 1
+                try:
+                    other_diff = seq[other_seq][0]
+                except IndexError:
+                    break
+                else:
+                    if high_mark < other_diff[LO]:
+                        break
+                    if high_mark == other_diff[LO] and (high_diff[0] != 'insert' or other_diff[0] != 'insert'):
+                        break
+
+                using[other_seq].append(other_diff)
+                seq[other_seq].pop(0)
+
+                if high_mark < other_diff[HI]:
+                    high_seq ^= 1
+                    high_diff = other_diff
+                    high_mark = high_diff[HI]
+
+            if len(using[0])==0:
+                if len(using[1])!=0:
+                    yield None, using[1][0]
+            elif len(using[1])==0:
+                assert len(using[0])==1
+                yield using[0][0], None
+            else:
+                block = self._merge_blocks( using )
+                l0, h0, l1, h1, l2, h2 = block
+                if h0-l0 == h2-l2 and texts[0][l0:h0] == texts[2][l2:h2]:
+                    # handle simple conflicts here (exact match)
+                    if l1 != h1 and l0 == h0:
+                        out0 = ('delete', block[2], block[3], block[0], block[1])
+                        out1 = ('delete', block[2], block[3], block[4], block[5])
+                    elif l1 != h1:
+                        out0 = ('replace', block[2], block[3], block[0], block[1])
+                        out1 = ('replace', block[2], block[3], block[4], block[5])
+                    else:
+                        out0 = ('insert', block[2], block[3], block[0], block[1])
+                        out1 = ('insert', block[2], block[3], block[4], block[5])
+                else:
+                    # here we will try to resolve more complex conflicts automatically... if possible
+                    out0 = ('conflict', block[2], block[3], block[0], block[1])
+                    out1 = ('conflict', block[2], block[3], block[4], block[5])
+                    if h1 == l1:
+                        # insert + insert -> insert if one contains the other
+                        l0 = using[0][0][LO + 2]
+                        l2 = using[1][0][LO + 2]
+                        len0 = using[0][0][HI+2] - l0
+                        len2 = using[1][0][HI+2] - l2
+                        shorter = min(len0, len2)
+                        h0 = l0 + shorter
+                        h2 = l2 + shorter
+                        if texts[0][l0:h0] == texts[2][l2:h2]:
+                            if len0 == shorter:
+                                out0 = None
+                                out1 = using[1][0]
+                            else:
+                                out0 = using[0][0]
+                                out1 = None
+                    elif h2 - l2 == h0 - l0 and h2 - l2 == h1 - l1:
+                        # replace + replace or mixed (same lenght) -> split into replcae/conflict chunks
+                        len0 = h0 - l0
+                        mergetype = 'conflict'
+                        mergelen = 0
+                        for i in range(len0+1):
+                            if i < len0:
+                                if texts[0][l0+i:l0 + i+1] == texts[2][l2 + i:l2 + i+1]:
+                                    newmergetype = 'replace'
+                                elif texts[0][l0+i:l0 + i+1] == texts[1][l1 + i:l1 + i+1]:
+                                    newmergetype = 'replace1'
+                                elif texts[2][l2+i:l2 + i+1] == texts[1][l1 + i:l1 + i+1]:
+                                    newmergetype = 'replace0'
+                                else:
+                                    newmergetype = 'conflict'
+                            if newmergetype != mergetype or i == len0:
+                                if mergelen:
+                                    out0 = None
+                                    out1 = None
+                                    if mergetype == 'replace0':
+                                        out0 = ('replace', l1 + i - mergelen, l1 + i, l0 + i - mergelen, l0 + i)
+                                    elif mergetype == 'replace1':
+                                        out1 = ('replace', l1 + i - mergelen, l1 + i, l2 + i - mergelen, l2 + i)
+                                    else:
+                                        out0 = (mergetype, l1 + i - mergelen, l1 + i, l0 + i - mergelen, l0 + i)
+                                        out1 = (mergetype, l1 + i - mergelen, l1 + i, l2 + i - mergelen, l2 + i)
+                                    yield out0, out1
+                                    mergelen = 0
+                            mergetype = newmergetype
+                            mergelen += 1
+                        continue
+                    else:
+                        type = using[0][0][0]
+                        for chunkarr in using:
+                            for chunk in chunkarr:
+                                if chunk[0] != type:
+                                    type = None
+                                    break
+                            if not type:
+                                break
+                        if type == 'delete':
+                            # (same type) delete + delete -> delete
+                            out0 = ('delete', block[2], block[3], block[0], block[1])
+                            out1 = ('delete', block[2], block[3], block[4], block[5])
+                yield out0, out1
+
+    def _apply_change(self, text, change, merged):
+        LO, HI = 1,2
+        if change[0] == 'insert':
+            for i in range(change[LO + 2], change[HI + 2]):
+                merged.insert( merged.get_end_iter(), text[i] + "\n")
+            return 0
+        elif change[0] == 'replace':
+            for i in range(change[LO + 2], change[HI + 2]):
+                merged.insert( merged.get_end_iter(), text[i] + "\n")
+            return change[HI] - change[LO]
+        else:
+            return change[HI] - change[LO]
+
+    def merge_file(self, texts, merged =None):
+        LO, HI = 1,2
+        diffs = self.diffs
+        if not merged:
+            merged = gtk.TextBuffer()
+        lastline = 0
+        for change in self._resolve_conflicts(diffs[0], diffs[1], texts):
+            low_mark = lastline
+            if change[0] != None:
+                low_mark = change[0][LO]
+            if change[1] != None:
+                if change[1][LO] > low_mark:
+                    low_mark = change[1][LO]
+            
+            for i in range(lastline, low_mark, 1):
+                merged.insert( merged.get_end_iter(), texts[1][i] + "\n")
+            lastline = low_mark
+            if change[0] != None and change[1] != None and change[0][0] == 'conflict':
+                high_mark = max(change[0][HI], change[1][HI])
+                if high_mark != low_mark:
+                    for i in range(low_mark, high_mark):
+                        merged.insert( merged.get_end_iter(), "????????????????????????\n")
+                else:
+                    maxinsert = max(change[0][HI + 2] - change[0][LO + 2], change[1][HI + 2] - change[1][LO + 2])
+                    for i in range(maxinsert):
+                        merged.insert( merged.get_end_iter(), "????????????????????????\n")
+                lastline += high_mark - lastline
+            elif change[0] != None:
+                lastline += self._apply_change(texts[0], change[0], merged)
+            else:
+                lastline += self._apply_change(texts[2], change[1], merged)
+
+        baselen = len(texts[1])
+        if lastline < baselen:
+            for i in range(lastline, baselen):
+                merged.insert( merged.get_end_iter(), texts[1][i])
+                if i != baselen - 1:
+                    merged.insert( merged.get_end_iter(), "\n")
+
Index: matchers.py
===================================================================
--- matchers.py	(revision 0)
+++ matchers.py	(revision 0)
@@ -0,0 +1,271 @@
+### Copyright (C) 2009 Piotr Piastucki <the_leech@users.berlios.de>
+
+### This program is free software; you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation; either version 2 of the License, or
+### (at your option) any later version.
+
+### This program is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+### GNU General Public License for more details.
+
+### You should have received a copy of the GNU General Public License
+### along with this program; if not, write to the Free Software
+### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import difflib
+
+class BaseMatcher(difflib.SequenceMatcher):
+    def __init__(self, a="", b=""):
+        """Construct BaseMatcher."""
+        self.origlines = self.a = a
+        self.newlines = self.b = b
+        self.matching_blocks = self.opcodes = None
+
+    def get_matching_blocks(self):
+        if self.matching_blocks is not None:
+            return self.matching_blocks
+
+        for i in self.initialise():
+            pass
+
+        return self.matching_blocks
+
+    def get_difference_opcodes(self):
+        return filter(lambda x: x[0]!="equal", self.get_opcodes())
+
+
+class MyersMatcher(BaseMatcher):
+    def __init__(self, a="", b=""):
+        """Construct an MyersMatcher."""
+        self.origlines = self.a = a
+        self.newlines = self.b = b
+        self.matching_blocks = self.opcodes = None
+
+    def initialise(self):
+        self.matching_blocks = matching_blocks = []
+        origlines = self.origlines
+        newlines = self.newlines
+        n = len(origlines)
+        m = len(newlines)
+        max = n + m + 1
+        size = 1 + 2 * max
+        middle = (size + 1) / 2
+        snakes = []
+        snakesnum = 0
+        diagonal = [0] * size
+        diagonal[middle + 1] = (0, None)
+
+        lastsnake = None
+        for d in range(max):
+            for k in range(-d, d + 1, 2):
+                kmiddle = middle + k
+                kplus = kmiddle + 1
+                kminus = kmiddle - 1
+                if k == -d or (k != d and diagonal[kminus] < diagonal[kplus]):
+                    x, prev = diagonal[kplus]
+                else:
+                    x, prev = diagonal[kminus]
+                    x += 1
+                y = x - k
+                snake = 0
+
+                while x < n and y < m and origlines[x] == newlines[y]:
+                    x += 1
+                    y += 1
+                    snake += 1
+
+                if snake:
+                    node = snakesnum
+                    snakes.append((prev, x - snake, y - snake, snake))
+                    snakesnum += 1
+                else:
+                    node = prev
+                diagonal[kmiddle] = (x, node)
+                if x >= n and y >= m:
+                    lastsnake = node
+                    break
+            if lastsnake != None:
+                break
+        if lastsnake != None:
+            yield None
+            node = lastsnake
+            matching_blocks = self.matching_blocks
+            while node != None:
+                node, x, y, snake = snakes[node]
+                matching_blocks.insert(0, (x, y, snake) )
+        matching_blocks.append( (n, m, 0) )
+        yield 1
+
+
+class LCSMatcher(BaseMatcher):
+    def __init__(self, isdyna=0, a="", b=""):
+        BaseMatcher.__init__(self, a, b)
+        """Construct LCSMatcher."""
+        self.isdyna = isdyna
+        self.lengths = [[0] * len(b), [0] * len(b)]
+
+    def initialise(self):
+        self.matching_blocks = []
+        self.matchingblocksize = 0
+        self.matchingblockleft = 0
+        self.matchingblockright = 0
+        if self.isdyna:
+            work = self._dynamic_lcs(0, len(self.origlines), 0, len(self.newlines))
+        else:
+            work = self._brute_force_initialise()
+        for i in work:
+            yield i
+        if self.isdyna:
+            self._save_last_match();
+        self.matching_blocks.append( (len(self.origlines), len(self.newlines), 0) )
+        yield 1
+
+    def _brute_force_initialise(self):
+        """Brute force implementation of LCS algorithm"""
+        self.matching_blocks = matching_blocks = []
+        origlines = self.origlines
+        newlines = self.newlines
+        llen = len(origlines)
+        rlen = len(newlines)
+        lengths = [0] * rlen
+        paths = []
+        for i in range(llen):
+            yield None
+            prevw = 0
+            prevnw = 0
+            path = []
+            paths.append(path)
+            for j in range(rlen):
+                prevn = lengths[j]
+                k = 0
+                if origlines[i] == newlines[j]:
+                    path.append(0)
+                    k = prevnw + 1
+                else:
+                    if prevw < prevn:
+                        k = prevn
+                        path.append(1)
+                    else:
+                        k = prevw
+                        path.append(2)
+                lengths[j] = k
+                prevnw = prevn
+                prevw = k
+        lcsright = rlen - 1
+        lcsleft = llen - 1
+        matchsize = 0
+        while lcsleft >=0 and lcsright >=0 :
+            path = paths[lcsleft][lcsright]
+            if path == 0:
+                matchsize += 1
+                lcsleft -= 1
+                lcsright -= 1
+            else:
+                if matchsize > 0:
+                    matching_blocks.insert(0, (lcsleft + 1, lcsright + 1, matchsize))
+                    matchsize = 0
+                if path == 1:
+                    lcsleft -= 1
+                else:
+                    lcsright -= 1
+        if matchsize > 0:
+            matching_blocks.insert(0, (0, 0, matchsize))
+        yield None
+        
+
+    def _save_last_match(self):
+        if self.matchingblocksize > 0:
+            self.matching_blocks.append((self.matchingblockleft, self.matchingblockright, self.matchingblocksize))
+            self.matchingblocksize = 0
+            self.matchingblockleft = 0
+            self.matchingblockright = 0
+
+    def _dynamic_lcs(self, lstart, lend, rstart, rend):
+        """Dynamic LCS"""
+        if rstart < rend and lstart < lend:
+            if (rstart + 1) == rend or (lstart + 1) == lend:
+                origlines = self.origlines
+                newlines = self.newlines
+                found = 0
+                for i in range(rstart, rend):
+                    for j in range(lstart, lend):
+                        if origlines[j] == newlines[i]:
+                            if (j != self.matchingblockleft + self.matchingblocksize) or (i != self.matchingblockright + self.matchingblocksize):
+                                self._save_last_match()
+                            if self.matchingblocksize == 0:
+                                self.matchingblockleft = j
+                                self.matchingblockright = i
+                            self.matchingblocksize += 1
+                            found = 1
+                            break
+                    if found:
+                        break
+            else:
+                lmid = (lstart + lend) / 2
+                origlines = self.origlines
+                newlines = self.newlines
+
+                forwardlengths = self.lengths[0]
+                for i in range(lstart, lmid):
+                    prevw = 0;
+                    prevnw = 0;
+                    for j in range(rstart, rend):
+                        if i == lstart:
+                            prevn = 0
+                        else:
+                            prevn = forwardlengths[j]
+                        if origlines[i] == newlines[j]:
+                            k = prevnw + 1
+                        else:
+                            k = prevw
+                            if k < prevn:
+                                k = prevn
+                        forwardlengths[j] = k
+                        prevnw = prevn
+                        prevw = k
+
+                reverselengths = self.lengths[1]
+                lstartidx = lend - 1
+                rstartidx = rend - 1
+                for i in range(lstartidx, lmid - 1, -1):
+                    preve = 0;
+                    prevse = 0;
+                    for j in range(rstartidx, rstart - 1, -1):
+                        if i == lstartidx:
+                            prevs = 0
+                        else:
+                            prevs = reverselengths[j]
+                        if origlines[i] == newlines[j]:
+                            k = prevse + 1
+                        else:
+                            k = preve
+                            if k < prevs:
+                                k = prevs
+                        reverselengths[j] = k
+                        prevse = prevs
+                        preve = k
+
+                lcs = -1
+                looplen = rend + rstart
+                for i in range(rstart + 1, rend):
+                # the closer to the middle the more natural it will look
+                    nlcs = forwardlengths[i - 1] + reverselengths[i]
+                    if (nlcs > lcs):
+                        lcs = nlcs
+                        rmid = i;
+                    elif (nlcs == lcs):
+                        distance1 = abs((looplen) - 2 * rmid)
+                        distance2 = abs((looplen) - 2 * i)
+                        if distance1 > distance2:
+                            rmid = i
+
+                work = self._dynamic_lcs(lstart, lmid, rstart, rmid)
+                for i in work:
+                    pass
+                work = self._dynamic_lcs(lmid, lend, rmid, rend)
+                for i in work:
+                    pass
+        yield None
+
Index: filediff.py
===================================================================
--- filediff.py	(revision 1329)
+++ filediff.py	(working copy)
@@ -14,6 +14,7 @@
 ### along with this program; if not, write to the Free Software
 ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+import merge
 import codecs
 import os
 from gettext import gettext as _
@@ -69,6 +70,7 @@
                 v.set_show_line_numbers(self.prefs.show_line_numbers)
                 v.set_insert_spaces_instead_of_tabs(self.prefs.spaces_instead_of_tabs)
                 srcviewer.set_tab_width(v, self.prefs.tab_size)
+        self.merge_mode = 0
         self.keymask = 0
         self.load_font()
         self.deleted_lines_pending = -1
@@ -76,6 +78,7 @@
         self.textview_focussed = None
         self.textview_overwrite_handlers = [ t.connect("toggle-overwrite", self.on_textview_toggle_overwrite) for t in self.textview ]
         self.textbuffer = [v.get_buffer() for v in self.textview]
+        self.textbuffer.append(gtk.TextBuffer())
         self.bufferdata = [MeldBufferData() for b in self.textbuffer]
         self.vscroll = [w.get_vscrollbar() for w in self.scrolledwindow]
         for i in range(3):
@@ -156,6 +159,9 @@
     def _connect_buffer_handlers(self):
         for textview in self.textview:
             textview.set_editable(1)
+        if self.merge_mode:
+            self.textview[0].set_editable(0)
+            self.textview[2].set_editable(0)
         for buf in self.textbuffer:
             id0 = buf.connect("insert-text", self.on_text_insert_text)
             id1 = buf.connect("delete-range", self.on_text_delete_range)
@@ -199,9 +205,14 @@
                 self.buf, self.textfilter = buf, textfilter
             def __getslice__(self, lo, hi):
                 b = self.buf
-                txt = b.get_text(b.get_iter_at_line(lo), b.get_iter_at_line(hi), 0)
-                txt = self.textfilter(txt)
-                return txt.split("\n")[:-1]
+                if hi >= b.get_line_count(): 
+                    txt = b.get_text(b.get_iter_at_line(lo), b.get_end_iter(), 0)
+                    txt = self.textfilter(txt)
+                    return txt.split("\n")[:]
+                else:
+                    txt = b.get_text(b.get_iter_at_line(lo), b.get_iter_at_line(hi), 0)
+                    txt = self.textfilter(txt)
+                    return txt.split("\n")[:-1]
         class FakeTextArray(object):
             def __init__(self, bufs, textfilter):
                 self.texts = [FakeText(b, textfilter) for b in  bufs]
@@ -479,12 +490,18 @@
         """Set num panes to len(files) and load each file given.
            If an element is None, the text of a pane is left as is.
         """
+        self.merge_mode = len(files) == 4
+        if self.merge_mode:
+            file1 = files[1]
+            files[1] = files[3]
+            files[3] = file1
         for i,f in enumerate(files):
             if f:
                 self.textbuffer[i].delete(*self.textbuffer[i].get_bounds())
                 absfile = os.path.abspath(f)
-                self.fileentry[i].set_filename(absfile)
-                self.fileentry[i].prepend_history(absfile)
+                if i < 3:
+                    self.fileentry[i].set_filename(absfile)
+                    self.fileentry[i].prepend_history(absfile)
                 bold, bnew = self.bufferdata[i], MeldBufferData(absfile)
                 if bold.filename == bnew.filename:
                     bnew.label = bold.label
@@ -494,16 +511,19 @@
         self.scheduler.add_task( self._set_files_internal(files).next )
 
     def _set_files_internal(self, files):
+        actualfiles = min(len(files), 3)
         yield _("[%s] Set num panes") % self.label_text
-        self.set_num_panes( len(files) )
+        self.set_num_panes( actualfiles )
         self._disconnect_buffer_handlers()
         self.linediffer.diffs = [[],[]]
         self.queue_draw()
         try_codecs = self.prefs.text_codecs.split() or ['utf_8', 'utf_16']
         yield _("[%s] Opening files") % self.label_text
-        panetext = ["\n"] * self.num_panes
+        panetext = ["\n"] * len(files)
         tasks = []
         for i,f in enumerate(files):
+            if self.merge_mode and i == 1:
+                continue
             buf = self.textbuffer[i]
             if f:
                 try:
@@ -580,6 +600,21 @@
         self.undosequence.clear()
         yield _("[%s] Computing differences") % self.label_text
         panetext = [self._filter_text(p) for p in panetext]
+        if self.merge_mode:
+            panetext[1] = panetext[3]
+            panetext = panetext[0:3]
+            mergelines = map(lambda x: x.split("\n"), panetext)
+            yield _("[%s] Merging files") % self.label_text
+            merger = merge.Merger()
+            step = merger.set_sequences_iter(*mergelines)
+            while step.next() == None:
+                yield 1
+            buf = self.textbuffer[1]
+            merger.merge_file(mergelines, buf)
+            self.linediffer = merger
+            panetext[1] = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), 0)
+            self.bufferdata[1].modified = 1
+            self.recompute_label()
         lines = map(lambda x: x.split("\n"), panetext)
         step = self.linediffer.set_sequences_iter(*lines)
         while step.next() == None:
@@ -590,7 +625,7 @@
         self.scheduler.add_task( self._update_highlighting( (0,lenseq[0]), (0,lenseq[1]) ).next )
         self._connect_buffer_handlers()
         if srcviewer:
-            for i in range(len(files)):
+            for i in range(actualfiles):
                 if files[i]:
                     srcviewer.set_highlighting_enabled_from_file(self.textbuffer[i], files[i], self.prefs.use_syntax_highlighting)
         yield 0
@@ -674,8 +709,10 @@
         for change in self.linediffer.single_changes(pane, self._get_texts()):
             if change[2] < start_line: continue
             if change[1] > end_line: break
-            if last_change and change[1] <= last_change[2]:
+            if last_change and change[1] < last_change[2]:
                 last_change = ("conflict", last_change[1], max(last_change[2],change[2]))
+            elif last_change and change[1] == last_change[2] and change[0] == last_change[0] and change[0] == 'insert':
+                last_change = ("conflict", last_change[1], max(last_change[2],change[2]))
             else:
                 if last_change:
                     draw_change(last_change)
@@ -1009,6 +1046,9 @@
 
     def _line_to_pixel(self, pane, line ):
         it = self.textbuffer[pane].get_iter_at_line(line)
+        if line >= self.textbuffer[pane].get_line_count():
+            y, h = self.textview[pane].get_line_yrange( it )
+            return y + h - 1
         return self.textview[pane].get_iter_location( it ).y
 
     def _line_to_pixel_plus_height(self, pane, line ):
@@ -1149,10 +1189,22 @@
             context.stroke()
 
             x = wtotal-self.pixbuf_apply0.get_width()
-            if c[0] in ("insert", "replace"):
-                paint_pixbuf_at(pix1, x, t0)
-            if c[0] in ("delete", "replace"):
-                paint_pixbuf_at(pix0, 0, f0)
+            if self.merge_mode:
+                if which:
+                    if c[0] in ("delete"):
+                        paint_pixbuf_at(self.pixbuf_delete, 0, f0)
+                    if c[0] in ("insert", "replace", "conflict"):
+                        paint_pixbuf_at(self.pixbuf_apply1, x, t0)
+                else:
+                    if c[0] in ("insert"):
+                        paint_pixbuf_at(self.pixbuf_delete, x, t0)
+                    if c[0] in ("delete", "replace", "conflict"):
+                        paint_pixbuf_at(self.pixbuf_apply0, 0, f0)
+            else:
+                if c[0] in ("insert", "replace"):
+                    paint_pixbuf_at(pix1, x, t0)
+                if c[0] in ("delete", "replace"):
+                    paint_pixbuf_at(pix0, 0, f0)
 
         # allow for scrollbar at end of textview
         mid = int(0.5 * self.textview[0].allocation.height) + 0.5
@@ -1190,6 +1242,8 @@
                 rect_x = wtotal - pix_width
             else:
                 return True
+            if self.merge_mode:
+                side = which
             src = which + side
             dst = which + 1 - side
             adj = self.scrolledwindow[src].get_vadjustment()
@@ -1201,9 +1255,12 @@
                     c = c[0], c[1]+c1,c[2]-c2, c[3]+c3,c[4]-c4
                     if c[1]==c[2] and c[3]==c[4]:
                         continue
+                if c[0] == "insert" and not self.merge_mode:
+                    continue
                 if c[0] == "insert":
-                    continue
-                h = self._line_to_pixel(src, c[1]) - adj.value
+                    h = self._line_to_pixel(dst, c[3]) - adj.value
+                else:
+                    h = self._line_to_pixel(src, c[1]) - adj.value
                 if h < 0: # find first visible chunk
                     continue
                 elif h > htotal: # we've gone past last visible
Index: meldapp.py
===================================================================
--- meldapp.py	(revision 1329)
+++ meldapp.py	(working copy)
@@ -831,8 +831,8 @@
         return doc
 
     def append_filediff(self, files):
-        assert len(files) in (1,2,3)
-        doc = filediff.FileDiff(self.prefs, len(files))
+        assert len(files) in (1,2,3,4)
+        doc = filediff.FileDiff(self.prefs, min(len(files), 3))
         seq = doc.undosequence
         seq.clear()
         seq.connect("can-undo", self.on_can_undo)
@@ -849,7 +849,10 @@
                     parent = self,
                     buttonstype = gtk.BUTTONS_OK)
         elif 1 in aredirs:
-            return self.append_dirdiff(paths, auto_compare)
+            if len(paths) == 4:
+                self.usage( _("Wrong number of arguments (Got %i)") % len(paths))
+            else:
+                return self.append_dirdiff(paths, auto_compare)
         else:
             return self.append_filediff(paths)
 
@@ -920,10 +923,10 @@
                           help=_("Creates a diff tab for up to 3 supplied files or directories."))
         options, args = parser.parse_args(rawargs)
         for files in options.diff:
-            if len(files) not in (1, 2, 3):
+            if len(files) not in (1, 2, 3, 4):
                 self.usage(_("Invalid number of arguments supplied for --diff."))
             self.append_diff(files)
-        if len(args) not in (0, 1, 2, 3):
+        if len(args) not in (0, 1, 2, 3, 4):
             self.usage(_("Wrong number of arguments (Got %i)") % len(args))
         else:
             tab = self.open_paths(args, options.auto_compare)
@@ -949,7 +952,7 @@
             else:
                 tab = self.append_vcview([a], auto_compare)
                     
-        elif len(paths) in (2,3):
+        elif len(paths) in (2,3,4):
             tab = self.append_diff(paths, auto_compare)
         return tab
 
