diff --git a/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java b/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java
index 335f000..b0afa1c 100644
--- a/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java
+++ b/src/org/openstreetmap/josm/gui/history/HistoryBrowserModel.java
@@ -6,18 +6,17 @@ import static org.openstreetmap.josm.tools.I18n.tr;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Observable;
 
-import javax.swing.table.DefaultTableModel;
+import javax.swing.table.AbstractTableModel;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.data.osm.Node;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.data.osm.Relation;
 import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
 import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
@@ -39,6 +38,7 @@ import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
 import org.openstreetmap.josm.gui.layer.Layer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.Diff;
 
 /**
  * This is the model used by the history browser.
@@ -79,10 +79,10 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
     private VersionTableModel versionTableModel;
     private TagTableModel currentTagTableModel;
     private TagTableModel referenceTagTableModel;
-    private NodeListTableModel currentNodeListTableModel;
-    private NodeListTableModel referenceNodeListTableModel;
     private RelationMemberTableModel currentRelationMemberTableModel;
     private RelationMemberTableModel referenceRelationMemberTableModel;
+    private DiffTableModel referenceNodeListTableModel;
+    private DiffTableModel currentNodeListTableModel;
 
     /**
      * constructor
@@ -91,8 +91,8 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
         versionTableModel = new VersionTableModel();
         currentTagTableModel = new TagTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
         referenceTagTableModel = new TagTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
-        currentNodeListTableModel = new NodeListTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
-        referenceNodeListTableModel = new NodeListTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
+        referenceNodeListTableModel = new DiffTableModel();
+        currentNodeListTableModel = new DiffTableModel();
         currentRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.CURRENT_POINT_IN_TIME);
         referenceRelationMemberTableModel = new RelationMemberTableModel(PointInTimeType.REFERENCE_POINT_IN_TIME);
 
@@ -145,7 +145,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
     protected boolean canShowAsLatest(OsmPrimitive primitive) {
         if (primitive == null) return false;
         if (primitive.isNew() || !primitive.isUsable()) return false;
-        
+
         //try creating a history primitive. if that fails, the primitive cannot be used.
         try {
             HistoryOsmPrimitive.forOsmPrimitive(primitive);
@@ -195,6 +195,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
     }
 
     protected void fireModelChange() {
+        initNodeListTableModels();
         setChanged();
         notifyObservers();
         versionTableModel.fireTableDataChanged();
@@ -215,9 +216,22 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
         referenceTagTableModel.initKeyList();
     }
 
-    protected void initNodeListTabeModels() {
-        currentNodeListTableModel.fireTableDataChanged();
+    /**
+     * Should be called everytime either reference of current changes to update the diff.
+     * TODO: Maybe rename to reflect this? eg. updateNodeListTableModels
+     */
+    protected void initNodeListTableModels() {
+
+        if(current.getType() != OsmPrimitiveType.WAY || reference.getType() != OsmPrimitiveType.WAY)
+            return;
+        TwoColumnDiff diff = new TwoColumnDiff(
+                ((HistoryWay)reference).getNodes().toArray(),
+                ((HistoryWay)current).getNodes().toArray());
+        referenceNodeListTableModel.setRows(diff.referenceDiff);
+        currentNodeListTableModel.setRows(diff.currentDiff);
+
         referenceNodeListTableModel.fireTableDataChanged();
+        currentNodeListTableModel.fireTableDataChanged();
     }
 
     protected void initMemberListTableModels() {
@@ -243,7 +257,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
         return null;
     }
 
-    public NodeListTableModel getNodeListTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
+    public DiffTableModel getNodeListTableModel(PointInTimeType pointInTimeType) throws IllegalArgumentException {
         CheckParameterUtil.ensureParameterNotNull(pointInTimeType, "pointInTimeType");
         if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME))
             return currentNodeListTableModel;
@@ -289,7 +303,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
 
         this.reference = reference;
         initTagTableModels();
-        initNodeListTabeModels();
+        initNodeListTableModels();
         initMemberListTableModels();
         setChanged();
         notifyObservers();
@@ -318,7 +332,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
             throw new IllegalArgumentException(tr("Failed to set current primitive. Current version {0} not available in history.", current.getVersion()));
         this.current = current;
         initTagTableModels();
-        initNodeListTabeModels();
+        initNodeListTableModels();
         initMemberListTableModels();
         setChanged();
         notifyObservers();
@@ -377,7 +391,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
      * The table model for the list of versions in the current history
      *
      */
-    public class VersionTableModel extends DefaultTableModel{
+    public class VersionTableModel extends AbstractTableModel {
 
         private VersionTableModel() {
         }
@@ -458,6 +472,11 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
             OsmPrimitive p = getEditLayer().data.getPrimitiveById(latest.getId(), latest.getType());
             return p;
         }
+
+        @Override
+        public int getColumnCount() {
+            return 1;
+        }
     }
 
     /**
@@ -465,7 +484,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
      * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
      *
      */
-    public class TagTableModel extends DefaultTableModel {
+    public class TagTableModel extends AbstractTableModel {
 
         private ArrayList<String> keys;
         private PointInTimeType pointInTimeType;
@@ -553,104 +572,10 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
         public boolean isReferencePointInTime() {
             return pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME);
         }
-    }
-
-    /**
-     * The table model for the nodes of the version at {@see PointInTimeType#REFERENCE_POINT_IN_TIME}
-     * or {@see PointInTimeType#CURRENT_POINT_IN_TIME}
-     *
-     */
-    public class NodeListTableModel extends DefaultTableModel {
-
-        private PointInTimeType pointInTimeType;
-
-        private NodeListTableModel(PointInTimeType pointInTimeType) {
-            this.pointInTimeType = pointInTimeType;
-        }
-
-        @Override
-        public int getRowCount() {
-            int n = 0;
-            if (current != null && current.getType().equals(OsmPrimitiveType.WAY)) {
-                n = ((HistoryWay)current).getNumNodes();
-            }
-            if (reference != null && reference.getType().equals(OsmPrimitiveType.WAY)) {
-                n = Math.max(n,((HistoryWay)reference).getNumNodes());
-            }
-            return n;
-        }
-
-        protected HistoryWay getWay() {
-            if (pointInTimeType.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
-                if (! current.getType().equals(OsmPrimitiveType.WAY))
-                    return null;
-                return (HistoryWay)current;
-            }
-            if (pointInTimeType.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
-                if (! reference.getType().equals(OsmPrimitiveType.WAY))
-                    return null;
-                return (HistoryWay)reference;
-            }
-
-            // should not happen
-            return null;
-        }
-
-        protected HistoryWay getOppositeWay() {
-            PointInTimeType opposite = pointInTimeType.opposite();
-            if (opposite.equals(PointInTimeType.CURRENT_POINT_IN_TIME)) {
-                if (! current.getType().equals(OsmPrimitiveType.WAY))
-                    return null;
-                return (HistoryWay)current;
-            }
-            if (opposite.equals(PointInTimeType.REFERENCE_POINT_IN_TIME)) {
-                if (! reference.getType().equals(OsmPrimitiveType.WAY))
-                    return null;
-                return (HistoryWay)reference;
-            }
-
-            // should not happen
-            return null;
-        }
 
         @Override
-        public Object getValueAt(int row, int column) {
-            HistoryWay way = getWay();
-            if (way == null)
-                return null;
-            if (row >= way.getNumNodes())
-                return null;
-            return way.getNodes().get(row);
-        }
-
-        public PrimitiveId getNodeId(int row) {
-            HistoryWay way = getWay();
-            if (way == null) return null;
-            if (row > way.getNumNodes()) return null;
-            return new SimplePrimitiveId(way.getNodeId(row), OsmPrimitiveType.NODE);
-        }
-
-        @Override
-        public boolean isCellEditable(int row, int column) {
-            return false;
-        }
-
-        public boolean isSameInOppositeWay(int row) {
-            HistoryWay thisWay = getWay();
-            HistoryWay oppositeWay = getOppositeWay();
-            if (thisWay == null || oppositeWay == null)
-                return false;
-            if (row >= oppositeWay.getNumNodes())
-                return false;
-            return thisWay.getNodeId(row) == oppositeWay.getNodeId(row);
-        }
-
-        public boolean isInOppositeWay(int row) {
-            HistoryWay thisWay = getWay();
-            HistoryWay oppositeWay = getOppositeWay();
-            if (thisWay == null || oppositeWay == null)
-                return false;
-            return oppositeWay.getNodes().contains(thisWay.getNodeId(row));
+        public int getColumnCount() {
+            return 1;
         }
     }
 
@@ -660,7 +585,7 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
      *
      */
 
-    public class RelationMemberTableModel extends DefaultTableModel {
+    public class RelationMemberTableModel extends AbstractTableModel {
 
         private PointInTimeType pointInTimeType;
 
@@ -747,6 +672,11 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
                 return false;
             return oppositeRelation.getMembers().contains(thisRelation.getMembers().get(row));
         }
+
+        @Override
+        public int getColumnCount() {
+            return 1;
+        }
     }
 
     protected void setLatest(HistoryOsmPrimitive latest) {
@@ -917,3 +847,142 @@ public class HistoryBrowserModel extends Observable implements LayerChangeListen
         }
     }
 }
+
+/**
+ * Simple model storing "diff cells" in a list. Could probably have used a DefaultTableModel instead..
+ * 
+ * {@see NodeListDiffTableCellRenderer}
+ */
+class DiffTableModel extends AbstractTableModel {
+    private List<TwoColumnDiff.Item> rows;
+
+    public void setRows(List<TwoColumnDiff.Item> rows) {
+        this.rows = rows;
+    }
+
+    public DiffTableModel(List<TwoColumnDiff.Item> rows) {
+        this.rows = rows;
+    }
+    public DiffTableModel() {
+        this.rows = new ArrayList<TwoColumnDiff.Item>();
+    }
+    @Override
+    public int getRowCount() {
+        return rows.size();
+    }
+
+    @Override
+    public int getColumnCount() {
+        return 1;
+    }
+
+    @Override
+    public TwoColumnDiff.Item getValueAt(int rowIndex, int columnIndex) {
+        return rows.get(rowIndex);
+    }
+}
+
+
+/// Feel free to move me somewhere else. Maybe a bit specific for josm.tools?
+/**
+ * Produces a "two column diff" of two lists. (same as diff -y)
+ * 
+ * Each list is annotated with the changes relative to the other, and "empty" cells are inserted so the lists are comparable item by item.
+ * 
+ * diff on [1 2 3 4] [1 a 4 5] yields:
+ * 
+ * item(SAME, 1)    item(SAME, 1)
+ * item(CHANGED, 2) item(CHANGED, 2)
+ * item(DELETED, 3) item(EMPTY)
+ * item(SAME, 4)    item(SAME, 4)
+ * item(EMPTY)      item(INSERTED, 5)
+ * 
+ * @author olejorgenb
+ */
+class TwoColumnDiff {
+    public static class Item {
+        public static final int INSERTED = 1;
+        public static final int DELETED = 2;
+        public static final int CHANGED = 3;
+        public static final int SAME = 4;
+        public static final int EMPTY = 5; // value should be null
+        public Item(int state, Object value) {
+            this.state = state;
+            this.value = state == EMPTY ? null : value;
+        }
+
+        public final Object value;
+        public final int state;
+    }
+
+    public ArrayList<Item> referenceDiff;
+    public ArrayList<Item> currentDiff;
+    Object[] reference;
+    Object[] current;
+
+    /**
+     * The arguments will _not_ be modified
+     */
+    public TwoColumnDiff(Object[] reference, Object[] current) {
+        this.reference = reference;
+        this.current = current;
+        referenceDiff = new ArrayList<Item>();
+        currentDiff = new ArrayList<Item>();
+        diff();
+    }
+    private void diff() {
+        Diff diff = new Diff(reference, current);
+        Diff.change script = diff.diff_2(false);
+        twoColumnDiffFromScript(script, reference, current);
+    }
+
+    /**
+     * The result from the diff algorithm is a "script" (a compressed description of the changes)
+     * This method expands this script into a full two column description.
+     */
+    private void twoColumnDiffFromScript(Diff.change script, Object[] a, Object[] b) {
+        int ia = 0;
+        int ib = 0;
+
+        while(script != null) {
+            int deleted = script.deleted;
+            int inserted = script.inserted;
+            while(ia < script.line0 && ib < script.line1){
+                // System.out.println(" "+a[ia] + "\t "+b[ib]);
+                Item cell = new Item(Item.SAME, a[ia]);
+                referenceDiff.add(cell);
+                currentDiff.add(cell);
+                ia++;
+                ib++;
+            }
+
+            while(inserted > 0 || deleted > 0) {
+                if(inserted > 0 && deleted > 0) {
+                    // System.out.println("="+a[ia] + "\t="+b[ib]);
+                    referenceDiff.add(new Item(Item.CHANGED, a[ia++]));
+                    currentDiff.add(new Item(Item.CHANGED, b[ib++]));
+                } else if(inserted > 0) {
+                    // System.out.println("\t+" + b[ib]);
+                    referenceDiff.add(new Item(Item.EMPTY, null));
+                    currentDiff.add(new Item(Item.INSERTED, b[ib++]));
+                } else if(deleted > 0) {
+                    // System.out.println("-"+a[ia]);
+                    referenceDiff.add(new Item(Item.DELETED, a[ia++]));
+                    currentDiff.add(new Item(Item.EMPTY, null));
+                }
+                inserted--;
+                deleted--;
+            }
+            script = script.link;
+        }
+        while(ia < a.length && ib < b.length) {
+            // System.out.println((ia < a.length ? " "+a[ia]+"\t" : "\t") + (ib < b.length ? " "+b[ib] : ""));
+            referenceDiff.add(new Item(Item.SAME, a[ia++]));
+            currentDiff.add(new Item(Item.SAME, b[ib++]));
+        }
+    }
+}
+
+
+
+
diff --git a/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java b/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java
index c8aae4e..8092ab7 100644
--- a/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java
+++ b/src/org/openstreetmap/josm/gui/history/NodeListTableCellRenderer.java
@@ -13,16 +13,12 @@ import javax.swing.table.TableCellRenderer;
 
 import org.openstreetmap.josm.tools.ImageProvider;
 
-/**
- * The {@see TableCellRenderer} for a list of nodes in [@see HistoryBrower}
- *
- *
- */
 public class NodeListTableCellRenderer extends JLabel implements TableCellRenderer {
 
     public final static Color BGCOLOR_EMPTY_ROW = new Color(234,234,234);
-    public final static Color BGCOLOR_NOT_IN_OPPOSITE = new Color(255,197,197);
-    public final static Color BGCOLOR_IN_OPPOSITE = new Color(255,234,213);
+    public final static Color BGCOLOR_DELETED = new Color(255,197,197);
+    public final static Color BGCOLOR_INSERTED = new Color(0xDD, 0xFF, 0xDD);
+    public final static Color BGCOLOR_CHANGED = new Color(255,234,213);
     public final static Color BGCOLOR_SELECTED = new Color(143,170,255);
 
     private ImageIcon nodeIcon;
@@ -33,23 +29,30 @@ public class NodeListTableCellRenderer extends JLabel implements TableCellRender
         setIcon(nodeIcon);
     }
 
-    protected void renderNode(HistoryBrowserModel.NodeListTableModel model, Long nodeId, int row, boolean isSelected) {
+    protected void renderNode(TwoColumnDiff.Item item, boolean isSelected) {
         String text = "";
         Color bgColor = Color.WHITE;
-        if (nodeId == null) {
+        setIcon(nodeIcon);
+        if (item.value != null) {
+            text = tr("Node {0}", item.value.toString());
+        }
+        switch(item.state) {
+        case TwoColumnDiff.Item.EMPTY:
             text = "";
             bgColor = BGCOLOR_EMPTY_ROW;
             setIcon(null);
-        } else {
-            text = tr("Node {0}", nodeId.toString());
-            setIcon(nodeIcon);
-            if (model.isSameInOppositeWay(row)) {
-                bgColor = Color.WHITE;
-            } else if (model.isInOppositeWay(row)) {
-                bgColor = BGCOLOR_IN_OPPOSITE;
-            } else {
-                bgColor = BGCOLOR_NOT_IN_OPPOSITE;
-            }
+            break;
+        case TwoColumnDiff.Item.CHANGED:
+            bgColor = BGCOLOR_CHANGED;
+            break;
+        case TwoColumnDiff.Item.INSERTED:
+            bgColor = BGCOLOR_INSERTED;
+            break;
+        case TwoColumnDiff.Item.DELETED:
+            bgColor = BGCOLOR_DELETED;
+            break;
+        default:
+            bgColor = BGCOLOR_EMPTY_ROW;
         }
         if (isSelected) {
             bgColor = BGCOLOR_SELECTED;
@@ -63,13 +66,7 @@ public class NodeListTableCellRenderer extends JLabel implements TableCellRender
         if (value == null)
             return this;
 
-        HistoryBrowserModel.NodeListTableModel model = getNodeListTableModel(table);
-        Long nodeId = (Long)value;
-        renderNode(model, nodeId, row, isSelected);
+        renderNode((TwoColumnDiff.Item)value, isSelected);
         return this;
     }
-
-    protected HistoryBrowserModel.NodeListTableModel getNodeListTableModel(JTable table) {
-        return (HistoryBrowserModel.NodeListTableModel) table.getModel();
-    }
-}
+}
\ No newline at end of file
diff --git a/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java b/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java
index 01bc7d9..c4dc41e 100644
--- a/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java
+++ b/src/org/openstreetmap/josm/gui/history/NodeListTableColumnModel.java
@@ -6,6 +6,7 @@ import static org.openstreetmap.josm.tools.I18n.tr;
 import javax.swing.table.DefaultTableColumnModel;
 import javax.swing.table.TableColumn;
 
+
 /**
  * The {@see TableColumnModel} for the table with the list of nodes.
  *
diff --git a/src/org/openstreetmap/josm/gui/history/NodeListViewer.java b/src/org/openstreetmap/josm/gui/history/NodeListViewer.java
index 2e4999d..092aa4a 100644
--- a/src/org/openstreetmap/josm/gui/history/NodeListViewer.java
+++ b/src/org/openstreetmap/josm/gui/history/NodeListViewer.java
@@ -17,14 +17,16 @@ import javax.swing.JPopupMenu;
 import javax.swing.JScrollPane;
 import javax.swing.JTable;
 import javax.swing.ListSelectionModel;
+import javax.swing.table.TableModel;
 
 import org.openstreetmap.josm.Main;
 import org.openstreetmap.josm.actions.AutoScaleAction;
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
 import org.openstreetmap.josm.data.osm.PrimitiveId;
+import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
 import org.openstreetmap.josm.data.osm.history.History;
 import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
-import org.openstreetmap.josm.gui.history.HistoryBrowserModel.NodeListTableModel;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
 import org.openstreetmap.josm.tools.ImageProvider;
 
@@ -273,6 +275,13 @@ public class NodeListViewer extends JPanel {
         }
     }
 
+    static private PrimitiveId primitiveIdAtRow(TableModel model, int row) {
+        DiffTableModel castedModel = (DiffTableModel) model;
+        Long id = (Long)castedModel.getValueAt(row, 0).value;
+        if(id == null) return null;
+        return new SimplePrimitiveId(id, OsmPrimitiveType.NODE);
+    }
+
     class PopupMenuLauncher extends MouseAdapter {
         private JTable table;
 
@@ -294,8 +303,10 @@ public class NodeListViewer extends JPanel {
             if (!e.isPopupTrigger()) return;
             Point p = e.getPoint();
             int row = table.rowAtPoint(p);
-            NodeListTableModel model = (NodeListTableModel) table.getModel();
-            PrimitiveId pid = model.getNodeId(row);
+
+            PrimitiveId pid = primitiveIdAtRow(table.getModel(), row);
+            if (pid == null)
+                return;
             popupMenu.prepare(pid);
             popupMenu.show(e.getComponent(), e.getX(), e.getY());
         }
@@ -310,15 +321,12 @@ public class NodeListViewer extends JPanel {
             showHistoryAction = new ShowHistoryAction();
         }
 
-        protected NodeListTableModel getModel() {
-            return (NodeListTableModel)table.getModel();
-        }
-
         @Override
         public void mouseClicked(MouseEvent e) {
             if (e.getClickCount() < 2) return;
             int row = table.rowAtPoint(e.getPoint());
-            PrimitiveId pid = getModel().getNodeId(row);
+            if(row <= 0) return;
+            PrimitiveId pid = primitiveIdAtRow(table.getModel(), row);
             if (pid == null)
                 return;
             showHistoryAction.setPrimitiveId(pid);
diff --git a/src/org/openstreetmap/josm/gui/history/VersionTable.java b/src/org/openstreetmap/josm/gui/history/VersionTable.java
index 41bc66f..ddaf720 100644
--- a/src/org/openstreetmap/josm/gui/history/VersionTable.java
+++ b/src/org/openstreetmap/josm/gui/history/VersionTable.java
@@ -71,7 +71,9 @@ public class VersionTable extends JTable implements Observer{
     class SelectionHandler implements ListSelectionListener {
         public void valueChanged(ListSelectionEvent e) {
             DefaultListSelectionModel model = (DefaultListSelectionModel)e.getSource();
-            if (model.getMinSelectionIndex() >= 0) {
+            // For some reason we receive multiple "adjusting" events here even when the source is a simple "set selection" action
+            // The last and proper event will have getValueIsAdjusting() == false
+            if (model.getMinSelectionIndex() >= 0 && e.getValueIsAdjusting() == false) {
                 handleSelectCurrentPointInTime(model.getMinSelectionIndex());
             }
         }
diff --git a/src/org/openstreetmap/josm/tools/Diff.java b/src/org/openstreetmap/josm/tools/Diff.java
new file mode 100644
index 0000000..1cdebed
--- /dev/null
+++ b/src/org/openstreetmap/josm/tools/Diff.java
@@ -0,0 +1,937 @@
+package org.openstreetmap.josm.tools;
+
+//// Taken from http://www.bmsi.com/java/#diff
+
+// http://www.bmsi.com/java/DiffPrint.java could also be useful
+
+/*
+ * $Log: Diff.java,v $
+ * Revision 1.7  2009/01/19 03:05:26  stuart
+ * Fix StackOverflow bug with heuristic on reported by Jimmy Han.
+ *
+ * Revision 1.6  2003/03/06 22:51:32  stuart
+ * Convert to CVS
+ *
+ * Revision 1.5  2002/07/19  19:14:40  stuart
+ * fix reverseScript, make change ctor public, update docs
+ *
+ * Revision 1.4  2002/04/09  17:53:39  stuart
+ * More flexible interface for diff() function.
+ *
+ * Revision 1.3  2000/03/03 21:58:03  stuart
+ * move discard_confusing_lines and shift_boundaries to class file_data
+ *
+ * Revision 1.2  2000/03/02  16:37:38  stuart
+ * Add GPL and copyright
+ *
+ */
+
+import java.util.Hashtable;
+
+/** A class to compare vectors of objects.  The result of comparison
+    is a list of <code>change</code> objects which form an
+    edit script.  The objects compared are traditionally lines
+    of text from two files.  Comparison options such as "ignore
+    whitespace" are implemented by modifying the <code>equals</code>
+    and <code>hashcode</code> methods for the objects compared.
+<p>
+   The basic algorithm is described in: </br>
+   "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
+   Algorithmica Vol. 1 No. 2, 1986, p 251.
+<p>
+   This class outputs different results from GNU diff 1.15 on some
+   inputs.  Our results are actually better (smaller change list, smaller
+   total size of changes), but it would be nice to know why.  Perhaps
+   there is a memory overwrite bug in GNU diff 1.15.
+
+  @author Stuart D. Gathman, translated from GNU diff 1.15
+    Copyright (C) 2000  Business Management Systems, Inc.
+<p>
+    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 1, or (at your option)
+    any later version.
+<p>
+    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.
+<p>
+    You should have received a copy of the <a href=COPYING.txt>
+    GNU General Public License</a>
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+public class Diff {
+
+    /** Prepare to find differences between two arrays.  Each element of
+      the arrays is translated to an "equivalence number" based on
+      the result of <code>equals</code>.  The original Object arrays
+      are no longer needed for computing the differences.  They will
+      be needed again later to print the results of the comparison as
+      an edit script, if desired.
+     */
+    public Diff(Object[] a,Object[] b) {
+        Hashtable h = new Hashtable(a.length + b.length);
+        filevec[0] = new file_data(a,h);
+        filevec[1] = new file_data(b,h);
+    }
+
+    /** 1 more than the maximum equivalence value used for this or its
+     sibling file. */
+    private int equiv_max = 1;
+
+    /** When set to true, the comparison uses a heuristic to speed it up.
+    With this heuristic, for files with a constant small density
+    of changes, the algorithm is linear in the file size.  */
+    public boolean heuristic = false;
+
+    /** When set to true, the algorithm returns a guarranteed minimal
+      set of changes.  This makes things slower, sometimes much slower. */
+    public boolean no_discards = false;
+
+    private int[] xvec, yvec; /* Vectors being compared. */
+    private int[] fdiag;      /* Vector, indexed by diagonal, containing
+                   the X coordinate of the point furthest
+                   along the given diagonal in the forward
+                   search of the edit matrix. */
+    private int[] bdiag;      /* Vector, indexed by diagonal, containing
+                   the X coordinate of the point furthest
+                   along the given diagonal in the backward
+                   search of the edit matrix. */
+    private int fdiagoff, bdiagoff;
+    private final file_data[] filevec = new file_data[2];
+    private int cost;
+    /** Snakes bigger than this are considered "big". */
+    private static final int SNAKE_LIMIT = 20;
+
+    /** Find the midpoint of the shortest edit script for a specified
+     portion of the two files.
+
+     We scan from the beginnings of the files, and simultaneously from the ends,
+     doing a breadth-first search through the space of edit-sequence.
+     When the two searches meet, we have found the midpoint of the shortest
+     edit sequence.
+
+     The value returned is the number of the diagonal on which the midpoint lies.
+     The diagonal number equals the number of inserted lines minus the number
+     of deleted lines (counting only lines before the midpoint).
+     The edit cost is stored into COST; this is the total number of
+     lines inserted or deleted (counting only lines before the midpoint).
+
+     This function assumes that the first lines of the specified portions
+     of the two files do not match, and likewise that the last lines do not
+     match.  The caller must trim matching lines from the beginning and end
+     of the portions it is going to specify.
+
+     Note that if we return the "wrong" diagonal value, or if
+     the value of bdiag at that diagonal is "wrong",
+     the worst this can do is cause suboptimal diff output.
+     It cannot cause incorrect diff output.  */
+
+    private int diag (int xoff, int xlim, int yoff, int ylim) {
+        final int[] fd = fdiag; // Give the compiler a chance.
+        final int[] bd = bdiag; // Additional help for the compiler.
+        final int[] xv = xvec;      // Still more help for the compiler.
+        final int[] yv = yvec;      // And more and more . . .
+        final int dmin = xoff - ylim;   // Minimum valid diagonal.
+        final int dmax = xlim - yoff;   // Maximum valid diagonal.
+        final int fmid = xoff - yoff;   // Center diagonal of top-down search.
+        final int bmid = xlim - ylim;   // Center diagonal of bottom-up search.
+        int fmin = fmid, fmax = fmid;   // Limits of top-down search.
+        int bmin = bmid, bmax = bmid;   // Limits of bottom-up search.
+        /* True if southeast corner is on an odd
+                     diagonal with respect to the northwest. */
+        final boolean odd = (fmid - bmid & 1) != 0;
+
+        fd[fdiagoff + fmid] = xoff;
+        bd[bdiagoff + bmid] = xlim;
+
+        for (int c = 1;; ++c)
+        {
+            int d;          /* Active diagonal. */
+            boolean big_snake = false;
+
+            /* Extend the top-down search by an edit step in each diagonal. */
+            if (fmin > dmin) {
+                fd[fdiagoff + --fmin - 1] = -1;
+            } else {
+                ++fmin;
+            }
+            if (fmax < dmax) {
+                fd[fdiagoff + ++fmax + 1] = -1;
+            } else {
+                --fmax;
+            }
+            for (d = fmax; d >= fmin; d -= 2)
+            {
+                int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];
+
+                if (tlo >= thi) {
+                    x = tlo + 1;
+                } else {
+                    x = thi;
+                }
+                oldx = x;
+                y = x - d;
+                while (x < xlim && y < ylim && xv[x] == yv[y]) {
+                    ++x; ++y;
+                }
+                if (x - oldx > SNAKE_LIMIT) {
+                    big_snake = true;
+                }
+                fd[fdiagoff + d] = x;
+                if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+                {
+                    cost = 2 * c - 1;
+                    return d;
+                }
+            }
+
+            /* Similar extend the bottom-up search. */
+            if (bmin > dmin) {
+                bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
+            } else {
+                ++bmin;
+            }
+            if (bmax < dmax) {
+                bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
+            } else {
+                --bmax;
+            }
+            for (d = bmax; d >= bmin; d -= 2)
+            {
+                int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];
+
+                if (tlo < thi) {
+                    x = tlo;
+                } else {
+                    x = thi - 1;
+                }
+                oldx = x;
+                y = x - d;
+                while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
+                    --x; --y;
+                }
+                if (oldx - x > SNAKE_LIMIT) {
+                    big_snake = true;
+                }
+                bd[bdiagoff + d] = x;
+                if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+                {
+                    cost = 2 * c;
+                    return d;
+                }
+            }
+
+            /* Heuristic: check occasionally for a diagonal that has made
+       lots of progress compared with the edit distance.
+       If we have any such, find the one that has made the most
+       progress and return it as if it had succeeded.
+
+       With this heuristic, for files with a constant small density
+       of changes, the algorithm is linear in the file size.  */
+
+            if (c > 200 && big_snake && heuristic)
+            {
+                int best = 0;
+                int bestpos = -1;
+
+                for (d = fmax; d >= fmin; d -= 2)
+                {
+                    int dd = d - fmid;
+                    int x = fd[fdiagoff + d];
+                    int y = x - d;
+                    int v = (x - xoff) * 2 - dd;
+                    if (v > 12 * (c + (dd < 0 ? -dd : dd)))
+                    {
+                        if (v > best
+                                && xoff + SNAKE_LIMIT <= x && x < xlim
+                                && yoff + SNAKE_LIMIT <= y && y < ylim)
+                        {
+                            /* We have a good enough best diagonal;
+               now insist that it end with a significant snake.  */
+                            int k;
+
+                            for (k = 1; xvec[x - k] == yvec[y - k]; k++)
+                                if (k == SNAKE_LIMIT)
+                                {
+                                    best = v;
+                                    bestpos = d;
+                                    break;
+                                }
+                        }
+                    }
+                }
+                if (best > 0)
+                {
+                    cost = 2 * c - 1;
+                    return bestpos;
+                }
+
+                best = 0;
+                for (d = bmax; d >= bmin; d -= 2)
+                {
+                    int dd = d - bmid;
+                    int x = bd[bdiagoff + d];
+                    int y = x - d;
+                    int v = (xlim - x) * 2 + dd;
+                    if (v > 12 * (c + (dd < 0 ? -dd : dd)))
+                    {
+                        if (v > best
+                                && xoff < x && x <= xlim - SNAKE_LIMIT
+                                && yoff < y && y <= ylim - SNAKE_LIMIT)
+                        {
+                            /* We have a good enough best diagonal;
+               now insist that it end with a significant snake.  */
+                            int k;
+
+                            for (k = 0; xvec[x + k] == yvec[y + k]; k++)
+                                if (k == SNAKE_LIMIT)
+                                {
+                                    best = v;
+                                    bestpos = d;
+                                    break;
+                                }
+                        }
+                    }
+                }
+                if (best > 0)
+                {
+                    cost = 2 * c - 1;
+                    return bestpos;
+                }
+            }
+        }
+    }
+
+    /** Compare in detail contiguous subsequences of the two files
+     which are known, as a whole, to match each other.
+
+     The results are recorded in the vectors filevec[N].changed_flag, by
+     storing a 1 in the element for each line that is an insertion or deletion.
+
+     The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
+
+     Note that XLIM, YLIM are exclusive bounds.
+     All line numbers are origin-0 and discarded lines are not counted.  */
+
+    private void compareseq (int xoff, int xlim, int yoff, int ylim) {
+        /* Slide down the bottom initial diagonal. */
+        while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
+            ++xoff; ++yoff;
+        }
+        /* Slide up the top initial diagonal. */
+        while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
+            --xlim; --ylim;
+        }
+
+        /* Handle simple cases. */
+        if (xoff == xlim) {
+            while (yoff < ylim) {
+                filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true;
+            }
+        } else if (yoff == ylim) {
+            while (xoff < xlim) {
+                filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true;
+            }
+        } else
+        {
+            /* Find a point of correspondence in the middle of the files.  */
+
+            int d = diag (xoff, xlim, yoff, ylim);
+            int c = cost;
+            int f = fdiag[fdiagoff + d];
+            int b = bdiag[bdiagoff + d];
+
+            if (c == 1)
+                /* This should be impossible, because it implies that
+           one of the two subsequences is empty,
+           and that case was handled above without calling `diag'.
+           Let's verify that this is true.  */
+                throw new IllegalArgumentException("Empty subsequence");
+            else
+            {
+                /* Use that point to split this problem into two subproblems.  */
+                compareseq (xoff, b, yoff, b - d);
+                /* This used to use f instead of b,
+           but that is incorrect!
+           It is not necessarily the case that diagonal d
+           has a snake from b to f.  */
+                compareseq (b, xlim, b - d, ylim);
+            }
+        }
+    }
+
+    /** Discard lines from one file that have no matches in the other file.
+     */
+
+    private void discard_confusing_lines() {
+        filevec[0].discard_confusing_lines(filevec[1]);
+        filevec[1].discard_confusing_lines(filevec[0]);
+    }
+
+    private boolean inhibit = false;
+
+    /** Adjust inserts/deletes of blank lines to join changes
+     as much as possible.
+     */
+
+    private void shift_boundaries() {
+        if (inhibit)
+            return;
+        filevec[0].shift_boundaries(filevec[1]);
+        filevec[1].shift_boundaries(filevec[0]);
+    }
+
+    public interface ScriptBuilder {
+        /** Scan the tables of which lines are inserted and deleted,
+     producing an edit script.
+   @param changed0 true for lines in first file which do not match 2nd
+   @param len0 number of lines in first file
+   @param changed1 true for lines in 2nd file which do not match 1st
+   @param len1 number of lines in 2nd file
+   @return a linked list of changes - or null
+         */
+        public change build_script(
+                boolean[] changed0,int len0,
+                boolean[] changed1,int len1
+        );
+    }
+
+    /** Scan the tables of which lines are inserted and deleted,
+     producing an edit script in reverse order.  */
+
+    static class ReverseScript implements ScriptBuilder {
+        public  change build_script(
+                final boolean[] changed0,int len0,
+                final boolean[] changed1,int len1)
+        {
+            change script = null;
+            int i0 = 0, i1 = 0;
+            while (i0 < len0 || i1 < len1) {
+                if (changed0[1+i0] || changed1[1+i1]) {
+                    int line0 = i0, line1 = i1;
+
+                    /* Find # lines changed here in each file.  */
+                    while (changed0[1+i0]) {
+                        ++i0;
+                    }
+                    while (changed1[1+i1]) {
+                        ++i1;
+                    }
+
+                    /* Record this change.  */
+                    script = new change(line0, line1, i0 - line0, i1 - line1, script);
+                }
+
+                /* We have reached lines in the two files that match each other.  */
+                i0++; i1++;
+            }
+
+            return script;
+        }
+    }
+
+    static class ForwardScript implements ScriptBuilder {
+        /** Scan the tables of which lines are inserted and deleted,
+       producing an edit script in forward order.  */
+        public change build_script(
+                final boolean[] changed0,int len0,
+                final boolean[] changed1,int len1)
+        {
+            change script = null;
+            int i0 = len0, i1 = len1;
+
+            while (i0 >= 0 || i1 >= 0)
+            {
+                if (changed0[i0] || changed1[i1])
+                {
+                    int line0 = i0, line1 = i1;
+
+                    /* Find # lines changed here in each file.  */
+                    while (changed0[i0]) {
+                        --i0;
+                    }
+                    while (changed1[i1]) {
+                        --i1;
+                    }
+
+                    /* Record this change.  */
+                    script = new change(i0, i1, line0 - i0, line1 - i1, script);
+                }
+
+                /* We have reached lines in the two files that match each other.  */
+                i0--; i1--;
+            }
+
+            return script;
+        }
+    }
+
+    /** Standard ScriptBuilders. */
+    public final static ScriptBuilder
+    forwardScript = new ForwardScript(),
+    reverseScript = new ReverseScript();
+
+    /* Report the differences of two files.  DEPTH is the current directory
+     depth. */
+    public final change diff_2(final boolean reverse) {
+        return diff(reverse ? reverseScript : forwardScript);
+    }
+
+    /** Get the results of comparison as an edit script.  The script
+     is described by a list of changes.  The standard ScriptBuilder
+     implementations provide for forward and reverse edit scripts.
+     Alternate implementations could, for instance, list common elements
+     instead of differences.
+     @param bld an object to build the script from change flags
+     @return the head of a list of changes
+     */
+    public change diff(final ScriptBuilder bld) {
+
+        /* Some lines are obviously insertions or deletions
+       because they don't match anything.  Detect them now,
+       and avoid even thinking about them in the main comparison algorithm.  */
+
+        discard_confusing_lines ();
+
+        /* Now do the main comparison algorithm, considering just the
+       undiscarded lines.  */
+
+        xvec = filevec[0].undiscarded;
+        yvec = filevec[1].undiscarded;
+
+        int diags =
+            filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
+        fdiag = new int[diags];
+        fdiagoff = filevec[1].nondiscarded_lines + 1;
+        bdiag = new int[diags];
+        bdiagoff = filevec[1].nondiscarded_lines + 1;
+
+        compareseq (0, filevec[0].nondiscarded_lines,
+                0, filevec[1].nondiscarded_lines);
+        fdiag = null;
+        bdiag = null;
+
+        /* Modify the results slightly to make them prettier
+       in cases where that can validly be done.  */
+
+        shift_boundaries ();
+
+        /* Get the results of comparison in the form of a chain
+       of `struct change's -- an edit script.  */
+        return bld.build_script(
+                filevec[0].changed_flag,
+                filevec[0].buffered_lines,
+                filevec[1].changed_flag,
+                filevec[1].buffered_lines
+        );
+
+    }
+
+    /** The result of comparison is an "edit script": a chain of change objects.
+     Each change represents one place where some lines are deleted
+     and some are inserted.
+
+     LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+     DELETED is the number of lines deleted here from file 0.
+     INSERTED is the number of lines inserted here in file 1.
+
+     If DELETED is 0 then LINE0 is the number of the line before
+     which the insertion was done; vice versa for INSERTED and LINE1.  */
+
+    public static class change {
+        /** Previous or next edit command. */
+        public change link;
+        /** # lines of file 1 changed here.  */
+        public final int inserted;
+        /** # lines of file 0 changed here.  */
+        public final int deleted;
+        /** Line number of 1st deleted line.  */
+        public final int line0;
+        /** Line number of 1st inserted line.  */
+        public final int line1;
+
+        /** Cons an additional entry onto the front of an edit script OLD.
+       LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+       DELETED is the number of lines deleted here from file 0.
+       INSERTED is the number of lines inserted here in file 1.
+
+       If DELETED is 0 then LINE0 is the number of the line before
+       which the insertion was done; vice versa for INSERTED and LINE1.  */
+        public change(int line0, int line1, int deleted, int inserted, change old) {
+            this.line0 = line0;
+            this.line1 = line1;
+            this.inserted = inserted;
+            this.deleted = deleted;
+            this.link = old;
+            //System.err.println(line0+","+line1+","+inserted+","+deleted);
+        }
+    }
+
+    /** Data on one input file being compared.
+     */
+
+    class file_data {
+
+        /** Allocate changed array for the results of comparison.  */
+        void clear() {
+            /* Allocate a flag for each line of each file, saying whether that line
+     is an insertion or deletion.
+     Allocate an extra element, always zero, at each end of each vector.
+             */
+            changed_flag = new boolean[buffered_lines + 2];
+        }
+
+        /** Return equiv_count[I] as the number of lines in this file
+       that fall in equivalence class I.
+         @return the array of equivalence class counts.
+         */
+        int[] equivCount() {
+            int[] equiv_count = new int[equiv_max];
+            for (int i = 0; i < buffered_lines; ++i) {
+                ++equiv_count[equivs[i]];
+            }
+            return equiv_count;
+        }
+
+        /** Discard lines that have no matches in another file.
+
+       A line which is discarded will not be considered by the actual
+       comparison algorithm; it will be as if that line were not in the file.
+       The file's `realindexes' table maps virtual line numbers
+       (which don't count the discarded lines) into real line numbers;
+       this is how the actual comparison algorithm produces results
+       that are comprehensible when the discarded lines are counted.
+<p>
+       When we discard a line, we also mark it as a deletion or insertion
+       so that it will be printed in the output.
+      @param f the other file
+         */
+        void discard_confusing_lines(file_data f) {
+            clear();
+            /* Set up table of which lines are going to be discarded. */
+            final byte[] discarded = discardable(f.equivCount());
+
+            /* Don't really discard the provisional lines except when they occur
+       in a run of discardables, with nonprovisionals at the beginning
+       and end.  */
+            filterDiscards(discarded);
+
+            /* Actually discard the lines. */
+            discard(discarded);
+        }
+
+        /** Mark to be discarded each line that matches no line of another file.
+       If a line matches many lines, mark it as provisionally discardable.
+       @see equivCount()
+       @param counts The count of each equivalence number for the other file.
+       @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
+        for each line
+         */
+
+        private byte[] discardable(final int[] counts) {
+            final int end = buffered_lines;
+            final byte[] discards = new byte[end];
+            final int[] equivs = this.equivs;
+            int many = 5;
+            int tem = end / 64;
+
+            /* Multiply MANY by approximate square root of number of lines.
+     That is the threshold for provisionally discardable lines.  */
+            while ((tem = tem >> 2) > 0) {
+                many *= 2;
+            }
+
+            for (int i = 0; i < end; i++)
+            {
+                int nmatch;
+                if (equivs[i] == 0) {
+                    continue;
+                }
+                nmatch = counts[equivs[i]];
+                if (nmatch == 0) {
+                    discards[i] = 1;
+                } else if (nmatch > many) {
+                    discards[i] = 2;
+                }
+            }
+            return discards;
+        }
+
+        /** Don't really discard the provisional lines except when they occur
+       in a run of discardables, with nonprovisionals at the beginning
+       and end.  */
+
+        private void filterDiscards(final byte[] discards) {
+            final int end = buffered_lines;
+
+            for (int i = 0; i < end; i++)
+            {
+                /* Cancel provisional discards not in middle of run of discards.  */
+                if (discards[i] == 2) {
+                    discards[i] = 0;
+                } else if (discards[i] != 0)
+                {
+                    /* We have found a nonprovisional discard.  */
+                    int j;
+                    int length;
+                    int provisional = 0;
+
+                    /* Find end of this run of discardable lines.
+           Count how many are provisionally discardable.  */
+                    for (j = i; j < end; j++)
+                    {
+                        if (discards[j] == 0) {
+                            break;
+                        }
+                        if (discards[j] == 2) {
+                            ++provisional;
+                        }
+                    }
+
+                    /* Cancel provisional discards at end, and shrink the run.  */
+                    while (j > i && discards[j - 1] == 2) {
+                        discards[--j] = 0; --provisional;
+                    }
+
+                    /* Now we have the length of a run of discardable lines
+           whose first and last are not provisional.  */
+                    length = j - i;
+
+                    /* If 1/4 of the lines in the run are provisional,
+           cancel discarding of all provisional lines in the run.  */
+                    if (provisional * 4 > length)
+                    {
+                        while (j > i)
+                            if (discards[--j] == 2) {
+                                discards[j] = 0;
+                            }
+                    }
+                    else
+                    {
+                        int consec;
+                        int minimum = 1;
+                        int tem = length / 4;
+
+                        /* MINIMUM is approximate square root of LENGTH/4.
+               A subrun of two or more provisionals can stand
+               when LENGTH is at least 16.
+               A subrun of 4 or more can stand when LENGTH >= 64.  */
+                        while ((tem = tem >> 2) > 0) {
+                            minimum *= 2;
+                        }
+                        minimum++;
+
+                        /* Cancel any subrun of MINIMUM or more provisionals
+               within the larger run.  */
+                        for (j = 0, consec = 0; j < length; j++)
+                            if (discards[i + j] != 2) {
+                                consec = 0;
+                            } else if (minimum == ++consec) {
+                                /* Back up to start of subrun, to cancel it all.  */
+                                j -= consec;
+                            } else if (minimum < consec) {
+                                discards[i + j] = 0;
+                            }
+
+                        /* Scan from beginning of run
+               until we find 3 or more nonprovisionals in a row
+               or until the first nonprovisional at least 8 lines in.
+               Until that point, cancel any provisionals.  */
+                        for (j = 0, consec = 0; j < length; j++)
+                        {
+                            if (j >= 8 && discards[i + j] == 1) {
+                                break;
+                            }
+                            if (discards[i + j] == 2) {
+                                consec = 0; discards[i + j] = 0;
+                            }
+                            else if (discards[i + j] == 0) {
+                                consec = 0;
+                            } else {
+                                consec++;
+                            }
+                            if (consec == 3) {
+                                break;
+                            }
+                        }
+
+                        /* I advances to the last line of the run.  */
+                        i += length - 1;
+
+                        /* Same thing, from end.  */
+                        for (j = 0, consec = 0; j < length; j++)
+                        {
+                            if (j >= 8 && discards[i - j] == 1) {
+                                break;
+                            }
+                            if (discards[i - j] == 2) {
+                                consec = 0; discards[i - j] = 0;
+                            }
+                            else if (discards[i - j] == 0) {
+                                consec = 0;
+                            } else {
+                                consec++;
+                            }
+                            if (consec == 3) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /** Actually discard the lines.
+      @param discards flags lines to be discarded
+         */
+        private void discard(final byte[] discards) {
+            final int end = buffered_lines;
+            int j = 0;
+            for (int i = 0; i < end; ++i)
+                if (no_discards || discards[i] == 0)
+                {
+                    undiscarded[j] = equivs[i];
+                    realindexes[j++] = i;
+                } else {
+                    changed_flag[1+i] = true;
+                }
+            nondiscarded_lines = j;
+        }
+
+        file_data(Object[] data,Hashtable h) {
+            buffered_lines = data.length;
+
+            equivs = new int[buffered_lines];
+            undiscarded = new int[buffered_lines];
+            realindexes = new int[buffered_lines];
+
+            for (int i = 0; i < data.length; ++i) {
+                Integer ir = (Integer)h.get(data[i]);
+                if (ir == null) {
+                    h.put(data[i],new Integer(equivs[i] = equiv_max++));
+                } else {
+                    equivs[i] = ir.intValue();
+                }
+            }
+        }
+
+        /** Adjust inserts/deletes of blank lines to join changes
+       as much as possible.
+
+       We do something when a run of changed lines include a blank
+       line at one end and have an excluded blank line at the other.
+       We are free to choose which blank line is included.
+       `compareseq' always chooses the one at the beginning,
+       but usually it is cleaner to consider the following blank line
+       to be the "change".  The only exception is if the preceding blank line
+       would join this change to other changes.
+      @param f the file being compared against
+         */
+
+        void shift_boundaries(file_data f) {
+            final boolean[] changed = changed_flag;
+            final boolean[] other_changed = f.changed_flag;
+            int i = 0;
+            int j = 0;
+            int i_end = buffered_lines;
+            int preceding = -1;
+            int other_preceding = -1;
+
+            for (;;)
+            {
+                int start, end, other_start;
+
+                /* Scan forwards to find beginning of another run of changes.
+         Also keep track of the corresponding point in the other file.  */
+
+                while (i < i_end && !changed[1+i])
+                {
+                    while (other_changed[1+j++]) {
+                        /* Non-corresponding lines in the other file
+               will count as the preceding batch of changes.  */
+                        other_preceding = j;
+                    }
+                    i++;
+                }
+
+                if (i == i_end) {
+                    break;
+                }
+
+                start = i;
+                other_start = j;
+
+                for (;;)
+                {
+                    /* Now find the end of this run of changes.  */
+
+                    while (i < i_end && changed[1+i]) {
+                        i++;
+                    }
+                    end = i;
+
+                    /* If the first changed line matches the following unchanged one,
+         and this run does not follow right after a previous run,
+         and there are no lines deleted from the other file here,
+         then classify the first changed line as unchanged
+         and the following line as changed in its place.  */
+
+                    /* You might ask, how could this run follow right after another?
+         Only because the previous run was shifted here.  */
+
+                    if (end != i_end
+                            && equivs[start] == equivs[end]
+                                                       && !other_changed[1+j]
+                                                                         && end != i_end
+                                                                         && !((preceding >= 0 && start == preceding)
+                                                                                 || (other_preceding >= 0
+                                                                                         && other_start == other_preceding)))
+                    {
+                        changed[1+end++] = true;
+                        changed[1+start++] = false;
+                        ++i;
+                        /* Since one line-that-matches is now before this run
+             instead of after, we must advance in the other file
+             to keep in synch.  */
+                        ++j;
+                    } else {
+                        break;
+                    }
+                }
+
+                preceding = i;
+                other_preceding = j;
+            }
+        }
+
+        /** Number of elements (lines) in this file. */
+        final int buffered_lines;
+
+        /** Vector, indexed by line number, containing an equivalence code for
+       each line.  It is this vector that is actually compared with that
+       of another file to generate differences. */
+        private final int[]     equivs;
+
+        /** Vector, like the previous one except that
+       the elements for discarded lines have been squeezed out.  */
+        final int[]    undiscarded;
+
+        /** Vector mapping virtual line numbers (not counting discarded lines)
+       to real ones (counting those lines).  Both are origin-0.  */
+        final int[]    realindexes;
+
+        /** Total number of nondiscarded lines. */
+        int         nondiscarded_lines;
+
+        /** Array, indexed by real origin-1 line number,
+       containing true for a line that is an insertion or a deletion.
+       The results of comparison are stored here.  */
+        boolean[]       changed_flag;
+
+    }
+}
