diff --git a/diffutil.py b/diffutil.py
index 8f9c63b..41df754 100644
--- a/diffutil.py
+++ b/diffutil.py
@@ -88,6 +88,9 @@ class Differ(object):
                 return i
         return len(self.diffs[whichdiffs])
 
+    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]
@@ -116,7 +119,7 @@ class Differ(object):
         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],
@@ -174,6 +177,21 @@ class Differ(object):
             high.append(highc - d[HI] + d[2+HI])
         return low[0], high[0], lowc, highc, low[1], high[1]
 
+    def _auto_merge(self, using, texts):
+        l0, h0, l1, h1, l2, h2 = self._merge_blocks( using )
+        if h0-l0 == h2-l2 and texts[0][l0:h0] == texts[2][l2:h2]:
+            if l1 != h1 and l0 == h0:
+                tag = "delete"
+            elif l1 != h1:
+                tag = "replace"
+            else:
+                tag = "insert"
+        else:
+            tag = "conflict"
+        out0 = (tag, l1, h1, l0, h0)
+        out1 = (tag, l1, h1, l2, h2)
+        yield out0, out1
+
     def _merge_diffs(self, seq0, seq1, texts):
         seq0, seq1 = seq0[:], seq1[:]
         seq = seq0, seq1
@@ -185,6 +203,11 @@ class Differ(object):
                 high_seq = 0
             else:
                 high_seq = int(seq0[0][LO] > seq1[0][LO])
+                if seq0[0][LO] == seq1[0][LO]:
+                    if seq0[0][0] == "insert":
+                        high_seq = 0
+                    elif seq1[0][0] == "insert":
+                        high_seq = 1
 
             high_diff = seq[high_seq].pop(0)
             high_mark = high_diff[HI]
@@ -197,6 +220,8 @@ class Differ(object):
                 other_diff = seq[other_seq][0]
                 if high_mark < other_diff[LO]:
                     break
+                if high_mark == other_diff[LO] and not (high_diff[0] == other_diff[0] == "insert"):
+                    break
 
                 using[other_seq].append(other_diff)
                 seq[other_seq].pop(0)
@@ -212,17 +237,8 @@ class Differ(object):
                 assert len(using[0])==1
                 yield using[0][0], None
             else:
-                l0, h0, l1, h1, l2, h2 = self._merge_blocks(using)
-                if h0-l0 == h2-l2 and texts[0][l0:h0] == texts[2][l2:h2]:
-                    if l1 != h1:
-                        tag = "replace"
-                    else:
-                        tag = "insert"
-                else:
-                    tag = "conflict"
-                out0 = (tag, l1, h1, l0, h0)
-                out1 = (tag, l1, h1, l2, h2)
-                yield out0, out1
+                for c in self._auto_merge(using, texts):
+                    yield c
 
     def set_sequences_iter(self, *sequences):
         assert 0 <= len(sequences) <= 3
@@ -231,7 +247,7 @@ class Differ(object):
         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
diff --git a/filediff.py b/filediff.py
index 0167032..e97cf83 100644
--- a/filediff.py
+++ b/filediff.py
@@ -173,6 +173,9 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             id4 = buf.connect("mark-set", self.on_textbuffer_mark_set)
             buf.handlers = id0, id1, id2, id3, id4
 
+    def _get_custom_status_text(self):
+        return None
+
     def _update_cursor_status(self, buf):
         def update():
             it = buf.get_iter_at_mark( buf.get_insert() )
@@ -181,6 +184,9 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             # Abbreviation for line, column so that it will fit in the status bar
             line_column = _("Ln %i, Col %i") % (it.get_line()+1, it.get_line_offset()+1)
             status = "%s : %s" % ( insert_overwrite, line_column )
+            custom_status = self._get_custom_status_text()
+            if custom_status:
+                status = "%s%s" % (status, custom_status)
             self.emit("status-changed", status)
             return False
         self.scheduler.add_task(update)
@@ -505,6 +511,12 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         self.textview[len(files) >= 2].grab_focus()
         self.scheduler.add_task( self._set_files_internal(files).next )
 
+    def _after_files_loaded(self, panetext):
+        yield 1
+
+    def _get_custom_textbuffer(self, index):
+        return self.textbuffer[index]
+
     def _set_files_internal(self, files):
         yield _("[%s] Set num panes") % self.label_text
         self.set_num_panes( len(files) )
@@ -516,7 +528,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         panetext = ["\n"] * self.num_panes
         tasks = []
         for i,f in enumerate(files):
-            buf = self.textbuffer[i]
+            buf = self._get_custom_textbuffer(i)
             if f:
                 try:
                     task = misc.struct(filename = f,
@@ -588,6 +600,8 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                         panetext[t.pane] = "".join(t.text)
             yield 1
         self.undosequence.clear()
+        for i in self._after_files_loaded(panetext):
+            yield i
         yield _("[%s] Computing differences") % self.label_text
         panetext = [self._filter_text(p) for p in panetext]
         lines = map(lambda x: x.split("\n"), panetext)
@@ -633,7 +647,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 if c and c[0] == "replace":
                     bufs = self.textbuffer[1], self.textbuffer[i*2]
                     #tags = [b.get_tag_table().lookup("replace line") for b in bufs]
-                    starts = [b.get_iter_at_line(l) for b,l in zip(bufs, (c[1],c[3])) ]
+                    starts = [get_iter_at_line_or_eof(b, l) for b,l in zip(bufs, (c[1],c[3])) ]
                     text1 = "\n".join( self._get_texts(raw=1)[1  ][c[1]:c[2]] ).encode("utf16")
                     text1 = struct.unpack("%iH"%(len(text1)/2), text1)[1:]
                     textn = "\n".join( self._get_texts(raw=1)[i*2][c[3]:c[4]] ).encode("utf16")
@@ -643,7 +657,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                     # For very long sequences, bail rather than trying a very slow comparison
                     inline_limit = 8000 # arbitrary constant
                     if len(text1) > inline_limit and len(textn) > inline_limit:
-                        ends = [b.get_iter_at_line(l) for b, l in zip(bufs, (c[2], c[4]))]
+                        ends = [get_iter_at_line_or_eof(b, l) for b, l in zip(bufs, (c[2], c[4]))]
                         for i in range(2):
                             bufs[i].apply_tag(tags[i], starts[i], ends[i])
                         continue
@@ -696,21 +710,13 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 context.rectangle(0, ypos0, width, ypos1 - ypos0)
                 context.fill()
 
-        last_change = None
         for change in self.linediffer.single_changes(pane, self._get_texts()):
             change, skip = self._consume_blank_lines(change, pane, change[5])
             if skip:
                 continue
             if change[2] < start_line: continue
             if change[1] > end_line: break
-            if last_change and change[1] <= last_change[2]:
-                last_change = ("conflict", last_change[1], max(last_change[2],change[2]))
-            else:
-                if last_change:
-                    draw_change(last_change)
-                last_change = change
-        if last_change:
-            draw_change(last_change)
+            draw_change(change)
 
         if textview.is_focus():
             context.set_line_width(3)
@@ -961,7 +967,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                         obegin = c[3]
                 fraction = (line - mbegin) / ((mend - mbegin) or 1)
                 other_line = (obegin + fraction * (oend - obegin))
-                it = self.textbuffer[i].get_iter_at_line(int(other_line))
+                it = get_iter_at_line_or_eof(self.textbuffer[i], int(other_line))
                 val, height = self.textview[i].get_line_yrange(it)
                 val -= (adj.page_size) * syncpoint
                 val += (other_line-int(other_line)) * height
@@ -1131,16 +1137,14 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 break
         return lo,hi
 
-        #
-        # linkmap drawing
-        #
-    def on_linkmap_expose_event(self, widget, event):
-        wtotal, htotal = widget.allocation.width, widget.allocation.height
-        context = widget.window.cairo_create()
-        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
-        context.clip()
-        context.set_line_width(1.0)
+    def paint_pixbuf_at(self, context, pixbuf, x, y):
+        context.translate(x, y)
+        context.set_source_pixbuf(pixbuf, 0, 0)
+        context.paint()
+        context.identity_matrix()
+
 
+    def _linkmap_draw_icon(self, context, which, change, x, f0, t0):
         if self.keymask & MASK_SHIFT:
             pix0 = self.pixbuf_delete
             pix1 = self.pixbuf_delete
@@ -1151,6 +1155,20 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             pix0 = self.pixbuf_apply0
             pix1 = self.pixbuf_apply1
 
+        if change in ("insert", "replace"):
+            self.paint_pixbuf_at(context, pix1, x, t0)
+        if change in ("delete", "replace"):
+            self.paint_pixbuf_at(context, pix0, 0, f0)
+
+        #
+        # linkmap drawing
+        #
+    def on_linkmap_expose_event(self, widget, event):
+        wtotal, htotal = widget.allocation.width, widget.allocation.height
+        context = widget.window.cairo_create()
+        context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
+        context.clip()
+        context.set_line_width(1.0)
         which = self.linkmap.index(widget)
         pix_start = [None] * self.num_panes
         pix_start[which  ] = self.textview[which  ].get_visible_rect().y
@@ -1163,12 +1181,6 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
         # For bezier control points
         x_steps = [-0.5, (1. / 3) * wtotal, (2. / 3) * wtotal, wtotal + 0.5]
 
-        def paint_pixbuf_at(pixbuf, x, y):
-            context.translate(x, y)
-            context.set_source_pixbuf(pixbuf, 0, 0)
-            context.paint()
-            context.identity_matrix()
-
         for c in self.linediffer.pair_changes(which, which+1, self._get_texts()):
             c, skip = self._consume_blank_lines(c, which, which + 1)
             if skip:
@@ -1206,10 +1218,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
             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)
+            self._linkmap_draw_icon(context, which, c[0], x, f0, t0)
 
         # allow for scrollbar at end of textview
         mid = int(0.5 * self.textview[0].allocation.height) + 0.5
@@ -1221,6 +1230,25 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
     def on_linkmap_scroll_event(self, area, event):
         self.next_diff(event.direction)
 
+    def _linkmap_process_event(self, event, which, side, htotal, rect_x, pix_width, pix_height):
+        src = which + side
+        dst = which + 1 - side
+        adj = self.scrolledwindow[src].get_vadjustment()
+        for c in self.linediffer.pair_changes(src, dst, self._get_texts()):
+            c, skip = self._consume_blank_lines(c, src, dst)
+            if skip:
+                continue
+            if c[0] == "insert":
+                continue
+            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
+                break
+            elif h < event.y and event.y < h + pix_height:
+                self.mouse_chunk = ( (src,dst), (rect_x, h, pix_width, pix_height), c)
+                break
+
     def on_linkmap_button_press_event(self, area, event):
         if event.button == 1:
             self.focus_before_click = None
@@ -1247,24 +1275,7 @@ class FileDiff(melddoc.MeldDoc, gnomeglade.Component):
                 rect_x = wtotal - pix_width
             else:
                 return True
-            src = which + side
-            dst = which + 1 - side
-            adj = self.scrolledwindow[src].get_vadjustment()
-
-            for c in self.linediffer.pair_changes(src, dst, self._get_texts()):
-                c, skip = self._consume_blank_lines(c, src, dst)
-                if skip:
-                    continue
-                if c[0] == "insert":
-                    continue
-                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
-                    break
-                elif h < event.y and event.y < h + pix_height:
-                    self.mouse_chunk = ( (src,dst), (rect_x, h, pix_width, pix_height), c)
-                    break
+            self._linkmap_process_event(event, which, side, htotal, rect_x, pix_width, pix_height)
             #print self.mouse_chunk
             return True
         return False
diff --git a/filemerge.py b/filemerge.py
new file mode 100644
index 0000000..291e99c
--- /dev/null
+++ b/filemerge.py
@@ -0,0 +1,129 @@
+### 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
+
+from gettext import gettext as _
+import os
+import gtk
+import merge
+import filediff
+
+MASK_SHIFT, MASK_CTRL = 1, 2
+
+class FileMerge(filediff.FileDiff):
+
+    def __init__(self, prefs, num_panes):
+        filediff.FileDiff.__init__(self, prefs, num_panes)
+        self.linediffer = merge.Merger()
+        self.hidden_textbuffer = gtk.TextBuffer()
+
+    def _connect_buffer_handlers(self):
+        filediff.FileDiff._connect_buffer_handlers(self)
+        self.textview[0].set_editable(0)
+        self.textview[2].set_editable(0)
+
+    def set_files(self, files):
+        self.ancestor_file = files[1]
+        self.merge_file = files[3]
+        files[1] = files[3]
+        filediff.FileDiff.set_files(self, files[:3])
+
+    def _set_files_internal(self, files):
+        files[1] = self.ancestor_file
+        for i in (filediff.FileDiff._set_files_internal(self, files[:3])):
+            yield i
+
+    def _get_custom_status_text(self):
+        return "   Conflicts: %i" % (self.linediffer.get_unresolved_count())
+
+    def _get_custom_textbuffer(self, index):
+        if index == 1:
+            return self.hidden_textbuffer
+        return self.textbuffer[index]
+
+    def set_buffer_writable(self, buf, yesno):
+        if buf == self.hidden_textbuffer:
+            buf = self.textbuffer[1]
+            yesno = True
+        pane = self.textbuffer.index(buf)
+        self.bufferdata[pane].writable = yesno
+        self.recompute_label()
+
+    def _after_files_loaded(self, panetext):
+        yield _("[%s] Computing differences") % self.label_text
+        lines = map(lambda x: x.split("\n"), panetext)
+        filteredpanetext = [self._filter_text(p) for p in panetext]
+        filteredlines = map(lambda x: x.split("\n"), filteredpanetext)
+        merger = merge.Merger()
+        step = merger.set_sequences_iter(*filteredlines)
+        while step.next() == None:
+            yield 1
+        yield _("[%s] Merging files") % self.label_text
+        for panetext[1] in merger.merge_file(filteredlines, lines):
+            yield 1
+        self.textbuffer[1].insert( self.textbuffer[1].get_end_iter(), panetext[1])
+        self.bufferdata[1].modified = 1
+        self.recompute_label()
+        yield 1
+
+    def _linkmap_draw_icon(self, context, which, change, x, f0, t0):
+        pix0 = self.pixbuf_delete
+        if which:
+            if self.keymask & MASK_CTRL:
+                pix1 = self.pixbuf_copy1
+            else:
+                pix1 = self.pixbuf_apply1
+        else:
+            if self.keymask & MASK_CTRL:
+                pix1 = self.pixbuf_copy0
+            else:
+                pix1 = self.pixbuf_apply0
+                
+        if which:
+            if change in ("delete"):
+                self.paint_pixbuf_at(context, pix0, 0, f0)
+            if change in ("insert", "replace", "conflict"):
+                self.paint_pixbuf_at(context, pix1, x, t0)
+        else:
+            if change in ("insert"):
+                self.paint_pixbuf_at(context, pix0, x, t0)
+            if change in ("delete", "replace", "conflict"):
+                self.paint_pixbuf_at(context, pix1, 0, f0)
+
+    def _linkmap_process_event(self, event, which, side, htotal, rect_x, pix_width, pix_height):
+        origsrc = which + side
+        src = 2 * which
+        dst = 1
+        srcadj = self.scrolledwindow[src].get_vadjustment()
+        dstadj = self.scrolledwindow[dst].get_vadjustment()
+        for c in self.linediffer.pair_changes(src, dst, self._get_texts()):
+            c, skip = self._consume_blank_lines(c, src, dst)
+            if skip:
+                continue
+            if c[0] == "insert":
+                if origsrc != 1:
+                    continue
+                h = self._line_to_pixel(dst, c[3]) - dstadj.value
+            else:
+                if origsrc == 1:
+                    continue
+                h = self._line_to_pixel(src, c[1]) - srcadj.value
+            if h < 0: # find first visible chunk
+                continue
+            elif h > htotal: # we've gone past last visible
+                break
+            elif h < event.y and event.y < h + pix_height:
+                self.mouse_chunk = ( (src,dst), (rect_x, h, pix_width, pix_height), c)
+                break
diff --git a/glade2/meldapp.glade b/glade2/meldapp.glade
index dd377dd..70be723 100644
--- a/glade2/meldapp.glade
+++ b/glade2/meldapp.glade
@@ -49,7 +49,7 @@
                 </child>
                 <child>
                   <widget class="GtkStatusbar" id="doc_status">
-                    <property name="width_request">175</property>
+                    <property name="width_request">200</property>
                     <property name="visible">True</property>
                   </widget>
                   <packing>
diff --git a/matchers.py b/matchers.py
new file mode 100644
index 0000000..c6aafc1
--- /dev/null
+++ b/matchers.py
@@ -0,0 +1,210 @@
+### 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 MyersMatcher(difflib.SequenceMatcher):
+
+    def __init__(self, a="", b=""):
+        self.origlines = a
+        self.newlines = b
+        self.matching_blocks = self.opcodes = None
+        #perf optimization switches
+        self.discard_lines = True
+        self.find_prefix_suffix = True
+
+    def get_matching_blocks(self):
+        if self.matching_blocks is None:
+            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())
+
+    def find_common_prefix(self, origlines, newlines):
+        pointermax = min(len(origlines), len(newlines))
+        pointermid = pointermax
+        pointermin = 0
+        while pointermin < pointermid:
+            if origlines[pointermin:pointermid] == newlines[pointermin:pointermid]:
+                pointermin = pointermid
+            else:
+                pointermax = pointermid
+            pointermid = int((pointermax - pointermin) / 2 + pointermin)
+        return pointermid
+
+    def find_common_suffix(self, origlines, newlines):
+        pointermax = min(len(origlines), len(newlines))
+        pointermid = pointermax
+        pointermin = 0
+        while pointermin < pointermid:
+            if (origlines[-pointermid:len(origlines)-pointermin] ==  newlines[-pointermid:len(newlines)-pointermin]):
+                pointermin = pointermid
+            else:
+                pointermax = pointermid
+            pointermid = int((pointermax - pointermin) / 2 + pointermin)
+        return pointermid
+
+    def initialise(self):
+        self.matching_blocks = matching_blocks = []
+        origlines = self.origlines
+        newlines = self.newlines
+        n = len(origlines)
+        m = len(newlines)
+
+        # 1. pre-processing optimizations
+
+        # remove common prefix and common suffix
+        common_prefix = common_suffix = 0
+        if self.find_prefix_suffix:
+            common_prefix = self.find_common_prefix(origlines, newlines)
+            if common_prefix > 0:
+                origlines = origlines[common_prefix:]
+                newlines = newlines[common_prefix:]
+                n -= common_prefix
+                m -= common_prefix
+
+            if n > 0 and m > 0:
+                common_suffix = self.find_common_suffix(origlines, newlines)
+                if common_suffix > 0:
+                    origlines = origlines[:n - common_suffix]
+                    newlines = newlines[:m - common_suffix]
+                    n -= common_suffix
+                    m -= common_suffix
+
+        # discard lines that do not match any line from the other file
+        lines_discarded = False
+        if self.discard_lines and n > 0 and m > 0:
+            origindex = {}
+            newindex = {}
+            origlines2 = []
+            newlines2 = []
+            j = 0
+            for i, newline in enumerate(newlines):
+                if newline in origlines:
+                    newlines2.append(newline)
+                    newindex[j] = i
+                    j += 1
+            k = 0
+            for i, origline in enumerate(origlines):
+                if origline in newlines:
+                    origlines2.append(origlines[i])
+                    origindex[k] = i
+                    k += 1
+            if m - j > 10 or n - k > 10:
+                lines_discarded = True
+                origlines = origlines2
+                newlines = newlines2
+                n = len(origlines)
+                m = len(newlines)
+
+        # 2. diff algorithm - search for longest middle snake
+        if n > 0 and m > 0:
+            n2 = 2 *n
+            m2 = - 2 *m
+            max = n + m + 1
+            size = 1 + 2 * max
+            middle = (size + 1) / 2
+            snakes = []
+            diagonal = [(-1, None)] * size
+            diagonal[middle + 1] = (0, None)
+            dmin = -m
+            dmax = n
+
+            lastsnake = None
+            for d in range(max):
+                if not d % 100:
+                    yield None
+                k1 = -d
+                if k1 < dmin:
+                    k1 = m2 - k1
+                k2 = d
+                if k2 > dmax:
+                    k2 = n2 - k2
+
+                k1 += middle
+                k2 += middle + 1
+                for k in range(k1, k2, 2):
+                    kplus = k + 1
+                    kminus = k - 1
+                    if diagonal[kminus][0] < diagonal[kplus][0]:
+                        x, node = diagonal[kplus]
+                    else:
+                        x, node = diagonal[kminus]
+                        x += 1
+                    y = x - k + middle
+                    snake = 0
+
+                    while x < n and y < m and origlines[x] == newlines[y]:
+                        x += 1
+                        y += 1
+                        snake += 1
+
+                    if snake:
+                        snakes.append((node, x - snake, y - snake, snake))
+                        node = len(snakes) - 1
+                    diagonal[k] = (x, node)
+                    if x >= n and y >= m:
+                        lastsnake = node
+                        break
+                if lastsnake != None:
+                    break
+
+            # 3. build list of matching blocks
+            while lastsnake != None:
+                lastsnake, x, y, snake = snakes[lastsnake]
+                if lines_discarded:
+                    # split snakes if needed because of discarded lines
+                    x += snake - 1
+                    y += snake - 1
+                    xprev = origindex[x]
+                    yprev = newindex[y]
+                    if snake > 1:
+                        newsnake = 1
+                        for i in range(1, snake):
+                            x -= 1
+                            y -= 1
+                            xnext = origindex[x]
+                            ynext = newindex[y]
+                            if (xprev - xnext != 1) or (yprev - ynext != 1):
+                                matching_blocks.insert(0, (xprev, yprev, newsnake) )
+                                newsnake = 0
+                            xprev = xnext
+                            yprev = ynext
+                            newsnake += 1
+                        matching_blocks.insert(0, (xprev + common_prefix, yprev + common_prefix, newsnake) )
+                    else:
+                        matching_blocks.insert(0, (xprev + common_prefix, yprev + common_prefix, snake) )
+                else:
+                    matching_blocks.insert(0, (x + common_prefix, y + common_prefix, snake) )
+        if common_prefix > 0:
+            matching_blocks.insert(0, (0, 0, common_prefix) )
+        if common_suffix:
+            # append common suffix, check if it can be shifted and merged with previous matching block
+            x = len(self.origlines) - common_suffix
+            y = len(self.newlines) - common_suffix
+            if len(matching_blocks) > 0:
+                lastblock = matching_blocks[-1]
+                if self.origlines[x:] == self.origlines[lastblock[0]+lastblock[2]:lastblock[0]+lastblock[2]+common_suffix] and \
+                self.origlines[x:] == self.newlines[lastblock[1]+lastblock[2]:lastblock[1]+lastblock[2]+common_suffix]:
+                    x = lastblock[0]
+                    y = lastblock[1]
+                    common_suffix = lastblock[2] + common_suffix
+                    del matching_blocks[-1]
+            matching_blocks.append((x, y, common_suffix) )
+        matching_blocks.append( (len(self.origlines), len(self.newlines), 0) )
+        yield 1
diff --git a/meldapp.py b/meldapp.py
index 4f5652d..dc49566 100644
--- a/meldapp.py
+++ b/meldapp.py
@@ -44,6 +44,7 @@ import vcview
 import dirdiff
 import task
 import vc
+import filemerge
 
 from sourceviewer import srcviewer
 
@@ -778,8 +779,11 @@ class MeldApp(gnomeglade.Component):
         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)
+        if len(files) == 4:
+            doc = filemerge.FileMerge(self.prefs, min(len(files), 3))
+        else:
+            doc = filediff.FileDiff(self.prefs, min(len(files), 3))
         seq = doc.undosequence
         seq.clear()
         seq.connect("can-undo", self.on_can_undo)
@@ -796,7 +800,10 @@ class MeldApp(gnomeglade.Component):
                     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)
 
@@ -867,10 +874,10 @@ class MeldApp(gnomeglade.Component):
                           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)
@@ -896,7 +903,7 @@ class MeldApp(gnomeglade.Component):
             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
 
diff --git a/merge.py b/merge.py
new file mode 100644
index 0000000..595056d
--- /dev/null
+++ b/merge.py
@@ -0,0 +1,212 @@
+### 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 diffutil
+import matchers
+
+class Merger(diffutil.Differ):
+    def __init__(self):
+        diffutil.Differ.__init__(self)
+        self.auto_merge = False
+        self.unresolved = []
+
+    def _get_matcher(self, a, b):
+        #return diffutil.IncrementalSequenceMatcher(None, a, b)
+        return matchers.MyersMatcher(a, b)
+
+    def _auto_merge(self, using, texts):
+        l0, h0, l1, h1, l2, h2 = self._merge_blocks( using )
+
+        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:
+                tag = "delete"
+            elif l1 != h1:
+                tag = "replace"
+            else:
+                tag = "insert"
+            out0 = (tag, l1, h1, l0, h0)
+            out1 = (tag, l1, h1, l2, h2)
+        else:
+            # here we will try to resolve more complex conflicts automatically... if possible
+            out0 = ('conflict', l1, h1, l0, h0)
+            out1 = ('conflict', l1, h1, l2, h2)
+            if self.auto_merge:
+                len0 = h0 - l0
+                len1 = h1 - l1
+                len2 = h2 - l2
+                if (len0 > 0 and len2 > 0) and (len0 == len1 or len2 == len1 or len1 == 0):
+                    matcher = self._get_matcher(texts[0][l0:h0], texts[2][l2:h2])
+                    for chunk in matcher.get_opcodes():
+                        s1 = l1
+                        e1 = l1
+                        if len0 == len1:
+                            s1 += chunk[1]
+                            e1 += chunk[2]
+                        elif len2 == len1:
+                            s1 += chunk[3]
+                            e1 += chunk[4]
+                        if chunk[0] == 'equal':
+                            out0 = ('replace', s1, e1, l0 + chunk[1], l0 + chunk[2])
+                            out1 = ('replace', s1, e1, l2 + chunk[3], l2 + chunk[4])
+                            yield out0, out1
+                        else:
+                            out0 = ('conflict', s1, e1, l0 + chunk[1], l0 + chunk[2])
+                            out1 = ('conflict', s1, e1, l2 + chunk[3], l2 + chunk[4])
+                            yield out0, out1
+                    return
+                else:
+                    # some tricks to resolve even more conflicts automatically
+                    # unfortunately the resulting chunks cannot be used to highlight changes
+                    # but hey, they are good enough to merge the resulting file :)
+                    chunktype = using[0][0][0]
+                    for chunkarr in using:
+                        for chunk in chunkarr:
+                            if chunk[0] != chunktype:
+                                chunktype = None
+                                break
+                        if not chunktype:
+                            break
+                    if chunktype == 'delete':
+                        # delete + delete (any length) -> split into delete/conflict
+                        seq0 = seq1 = None
+                        while 1:
+                            if seq0 == None:
+                                try:
+                                    seq0 = using[0].pop(0)
+                                    i0 = seq0[1]
+                                    end0 = seq0[4]
+                                except IndexError:
+                                    break
+                            if seq1 == None:
+                                try:
+                                    seq1 = using[1].pop(0)
+                                    i1 = seq1[1]
+                                    end1 = seq1[4]
+                                except IndexError:
+                                    break
+                            highstart = max(i0, i1)
+                            if i0 != i1:
+                                out0 = ('conflict', i0 - highstart + i1, highstart, seq0[3] - highstart + i1, seq0[3])
+                                out1 = ('conflict', i1 - highstart + i0, highstart, seq1[3] - highstart + i0, seq1[3])
+                                yield out0, out1
+                            lowend = min(seq0[2], seq1[2])
+                            if highstart != lowend:
+                                out0 = ('delete', highstart, lowend, seq0[3], seq0[4])
+                                out1 = ('delete', highstart, lowend, seq1[3], seq1[4])
+                                yield out0, out1
+                            i0 = i1 = lowend
+                            if lowend == seq0[2]:
+                                seq0 = None
+                            if lowend == seq1[2]:
+                                seq1 = None
+
+                        if seq0:
+                            out0 = ('conflict', i0, seq0[2], seq0[3], seq0[4])
+                            out1 = ('conflict', i1, i1 + seq0[2] - i0, end1, end1 + seq0[2] - i0)
+                            yield out0, out1
+                        elif seq1:
+                            out0 = ('conflict', i0, i0 + seq1[2] - i1, end0, end0 + seq2[2] - i1)
+                            out1 = ('conflict', i1, seq1[2], seq1[3], seq1[4])
+                            yield out0, out1
+                        return
+        yield out0, out1
+
+    def change_sequence(self, sequence, startidx, sizechange, texts):
+        if sequence == 1:
+            lo = 0
+            for c in self.unresolved:
+                if startidx <= c:
+                    break
+                lo += 1
+            if lo < len(self.unresolved):
+                hi = lo
+                if sizechange < 0:
+                    for c in self.unresolved[lo:]:
+                        if startidx-sizechange <=c:
+                            break
+                        hi += 1
+                elif sizechange == 0 and startidx == self.unresolved[lo]:
+                    hi += 1
+
+                if hi < len(self.unresolved):
+                    self.unresolved[hi:] = [ c + sizechange for c in self.unresolved[hi:] ]
+                self.unresolved[lo:hi] = []
+
+        return diffutil.Differ.change_sequence(self, sequence, startidx, sizechange, texts)
+
+    def get_unresolved_count(self):
+        return len(self.unresolved)
+
+    def _apply_change(self, text, change, mergedtext):
+        LO, HI = 1,2
+        if change[0] == 'insert':
+            for i in range(change[LO + 2], change[HI + 2]):
+                mergedtext.append(text[i])
+            return 0
+        elif change[0] == 'replace':
+            for i in range(change[LO + 2], change[HI + 2]):
+                mergedtext.append(text[i])
+            return change[HI] - change[LO]
+        else:
+            return change[HI] - change[LO]
+
+    def merge_file(self, filteredtexts, texts):
+        LO, HI = 1,2
+        self.auto_merge = True
+        self.unresolved = unresolved = []
+        diffs = self.diffs
+        lastline = 0
+        mergedline = 0
+        mergedtext = []
+        for change in self._merge_diffs(diffs[0], diffs[1], filteredtexts):
+            yield None
+            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):
+                mergedtext.append(texts[1][i])
+            mergedline += low_mark - lastline
+            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 low_mark < high_mark:
+                    for i in range(low_mark, high_mark):
+                        mergedtext.append("(??)" + texts[1][i])
+                        unresolved.append(mergedline)
+                        mergedline += 1
+                else:
+                    #conflictsize = min(1, max(change[0][HI + 2] - change[0][LO + 2], change[1][HI + 2] - change[1][LO + 2]))
+                    #for i in range(conflictsize):
+                    mergedtext.append("(??)")
+                    unresolved.append(mergedline)
+                    mergedline += 1
+                lastline = high_mark
+            elif change[0] != None:
+                lastline += self._apply_change(texts[0], change[0], mergedtext)
+                mergedline += change[0][HI + 2] - change[0][LO + 2]
+            else:
+                lastline += self._apply_change(texts[2], change[1], mergedtext)
+                mergedline += change[1][HI + 2] - change[1][LO + 2]
+        baselen = len(texts[1])
+        for i in range(lastline, baselen, 1):
+            mergedtext.append(texts[1][i])
+
+        self.auto_merge = False
+        yield "\n".join(mergedtext)
