diff --git a/src/libs/utils/changeset.cpp b/src/libs/utils/changeset.cpp
index 614fbbc983d005ce89588890e80e82f7bc7b7b3c..2f8ad44a490617f239452f2735fe09053177333c 100644
--- a/src/libs/utils/changeset.cpp
+++ b/src/libs/utils/changeset.cpp
@@ -44,196 +44,312 @@
 namespace Utils {
 
 ChangeSet::ChangeSet()
-    : m_string(0), m_cursor(0)
+    : m_string(0), m_cursor(0), m_error(false)
 {
 }
 
 static bool overlaps(int posA, int lengthA, int posB, int lengthB) {
-    return (posA < posB + lengthB && posA + lengthA > posB + lengthB)
-            || (posA < posB && posA + lengthA > posB);
+    if (lengthB > 0) {
+        return
+                // right edge of B contained in A
+                (posA < posB + lengthB && posA + lengthA >= posB + lengthB)
+                // left edge of B contained in A
+                || (posA <= posB && posA + lengthA > posB)
+                // A contained in B
+                || (posB < posA && posB + lengthB > posA + lengthA);
+    } else {
+        return (posB > posA && posB < posA + lengthA);
+    }
 }
 
 bool ChangeSet::hasOverlap(int pos, int length)
 {
-    {
-        QListIterator<Replace> i(m_replaceList);
-        while (i.hasNext()) {
-            const Replace &cmd = i.next();
-            if (overlaps(pos, length, cmd.pos, cmd.length))
+    QListIterator<EditOp> i(m_operationList);
+    while (i.hasNext()) {
+        const EditOp &cmd = i.next();
+
+        switch (cmd.type) {
+        case EditOp::Replace:
+            if (overlaps(pos, length, cmd.pos1, cmd.length))
                 return true;
-        }
-    }
-    {
-        QListIterator<Move> i(m_moveList);
-        while (i.hasNext()) {
-            const Move &cmd = i.next();
-            if (overlaps(pos, length, cmd.pos, cmd.length))
+            break;
+
+        case EditOp::Move:
+            if (overlaps(pos, length, cmd.pos1, cmd.length))
+                return true;
+            if (cmd.pos2 > pos && cmd.pos2 < pos + length)
                 return true;
+            break;
+
+        case EditOp::Insert:
+            if (cmd.pos1 > pos && cmd.pos1 < pos + length)
+                return true;
+            break;
+
+        case EditOp::Remove:
+            if (overlaps(pos, length, cmd.pos1, cmd.length))
+                return true;
+            break;
+
+        case EditOp::Flip:
+            if (overlaps(pos, length, cmd.pos1, cmd.length))
+                return true;
+            if (overlaps(pos, length, cmd.pos2, cmd.length))
+                return true;
+            break;
+
+        case EditOp::Copy:
+            if (overlaps(pos, length, cmd.pos1, cmd.length))
+                return true;
+            if (cmd.pos2 > pos && cmd.pos2 < pos + length)
+                return true;
+            break;
+
+        case EditOp::Unset:
+            break;
         }
-        return false;
     }
-}
 
-bool ChangeSet::hasMoveInto(int pos, int length)
-{
-    QListIterator<Move> i(m_moveList);
-    while (i.hasNext()) {
-        const Move &cmd = i.next();
-        if (cmd.to >= pos && cmd.to < pos + length)
-            return true;
-    }
     return false;
 }
 
 bool ChangeSet::isEmpty() const
 {
-    if (m_replaceList.isEmpty() && m_moveList.isEmpty())
-        return true;
+    return m_operationList.isEmpty();
+}
 
-    return false;
+QList<ChangeSet::EditOp> ChangeSet::operationList() const
+{
+    return m_operationList;
+}
+
+void ChangeSet::clear()
+{
+    m_string = 0;
+    m_cursor = 0;
+    m_operationList.clear();
+    m_error = false;
 }
 
-QList<ChangeSet::Replace> ChangeSet::replaceList() const
+bool ChangeSet::replace(int pos, int length, const QString &replacement)
 {
-    return m_replaceList;
+    if (hasOverlap(pos, length))
+        m_error = true;
+
+    EditOp cmd(EditOp::Replace);
+    cmd.pos1 = pos;
+    cmd.length = length;
+    cmd.text = replacement;
+    m_operationList += cmd;
+
+    return !m_error;
 }
 
-QList<ChangeSet::Move> ChangeSet::moveList() const
+bool ChangeSet::move(int pos, int length, int to)
 {
-    return m_moveList;
+    if (hasOverlap(pos, length)
+        || hasOverlap(to, 0)
+        || overlaps(pos, length, to, 0))
+        m_error = true;
+
+    EditOp cmd(EditOp::Move);
+    cmd.pos1 = pos;
+    cmd.length = length;
+    cmd.pos2 = to;
+    m_operationList += cmd;
+
+    return !m_error;
 }
 
-void ChangeSet::clear()
+bool ChangeSet::insert(int pos, const QString &text)
 {
-    m_string = 0;
-    m_cursor = 0;
-    m_replaceList.clear();
-    m_moveList.clear();
+    if (hasOverlap(pos, 0))
+        m_error = true;
+
+    EditOp cmd(EditOp::Insert);
+    cmd.pos1 = pos;
+    cmd.text = text;
+    m_operationList += cmd;
+
+    return !m_error;
 }
 
-void ChangeSet::replace(int pos, int length, const QString &replacement)
+bool ChangeSet::remove(int pos, int length)
 {
-    Q_ASSERT(!hasOverlap(pos, length));
-    Q_ASSERT(!hasMoveInto(pos, length));
+    if (hasOverlap(pos, length))
+        m_error = true;
 
-    Replace cmd;
-    cmd.pos = pos;
+    EditOp cmd(EditOp::Remove);
+    cmd.pos1 = pos;
     cmd.length = length;
-    cmd.replacement = replacement;
-    m_replaceList += cmd;
+    m_operationList += cmd;
+
+    return !m_error;
 }
 
-void ChangeSet::move(int pos, int length, int to)
+bool ChangeSet::flip(int pos1, int length, int pos2)
 {
-    Q_ASSERT(!hasOverlap(pos, length));
+    if (hasOverlap(pos1, length)
+        || hasOverlap(pos2, length)
+        || overlaps(pos1, length, pos2, length))
+        m_error = true;
 
-    Move cmd;
-    cmd.pos = pos;
+    EditOp cmd(EditOp::Flip);
+    cmd.pos1 = pos1;
     cmd.length = length;
-    cmd.to = to;
-    m_moveList += cmd;
+    cmd.pos2 = pos2;
+    m_operationList += cmd;
+
+    return !m_error;
 }
 
-void ChangeSet::doReplace(const Replace &replace)
+bool ChangeSet::copy(int pos, int length, int to)
 {
-    int diff = replace.replacement.size() - replace.length;
-    {
-        QMutableListIterator<Replace> i(m_replaceList);
-        while (i.hasNext()) {
-            Replace &c = i.next();
-            if (replace.pos < c.pos)
-                c.pos += diff;
-            else if (replace.pos + replace.length < c.pos + c.length)
-                c.length += diff;
-        }
-    }
+    if (hasOverlap(pos, length)
+        || hasOverlap(to, 0)
+        || overlaps(pos, length, to, 0))
+        m_error = true;
+
+    EditOp cmd(EditOp::Copy);
+    cmd.pos1 = pos;
+    cmd.length = length;
+    cmd.pos2 = to;
+    m_operationList += cmd;
+
+    return !m_error;
+}
+
+void ChangeSet::doReplace(const EditOp &replace, QList<EditOp> *replaceList)
+{
+    Q_ASSERT(replace.type == EditOp::Replace);
+
+    int diff = replace.text.size() - replace.length;
     {
-        QMutableListIterator<Move> i(m_moveList);
+        QMutableListIterator<EditOp> i(*replaceList);
         while (i.hasNext()) {
-            Move &c = i.next();
-            if (replace.pos < c.pos)
-                c.pos += diff;
-            else if (replace.pos + replace.length < c.pos + c.length)
-                c.length += diff;
-
-            if (replace.pos < c.to)
-                c.to += diff;
+            EditOp &c = i.next();
+            if (replace.pos1 <= c.pos1)
+                c.pos1 += diff;
         }
     }
 
     if (m_string) {
-        m_string->replace(replace.pos, replace.length, replace.replacement);
+        m_string->replace(replace.pos1, replace.length, replace.text);
     } else if (m_cursor) {
-        m_cursor->setPosition(replace.pos);
-        m_cursor->setPosition(replace.pos + replace.length, QTextCursor::KeepAnchor);
-        m_cursor->insertText(replace.replacement);
+        m_cursor->setPosition(replace.pos1);
+        m_cursor->setPosition(replace.pos1 + replace.length, QTextCursor::KeepAnchor);
+        m_cursor->insertText(replace.text);
     }
 }
 
-void ChangeSet::doMove(const Move &move)
+void ChangeSet::convertToReplace(const EditOp &op, QList<EditOp> *replaceList)
 {
-    QString text;
-    if (m_string) {
-        text = m_string->mid(move.pos, move.length);
-    } else if (m_cursor) {
-        m_cursor->setPosition(move.pos);
-        m_cursor->setPosition(move.pos + move.length, QTextCursor::KeepAnchor);
-        text = m_cursor->selectedText();
-    }
+    EditOp replace1(EditOp::Replace);
+    EditOp replace2(EditOp::Replace);
+
+    switch (op.type) {
+    case EditOp::Replace:
+        replaceList->append(op);
+        break;
+
+    case EditOp::Move:
+        replace1.pos1 = op.pos1;
+        replace1.length = op.length;
+        replaceList->append(replace1);
+
+        replace2.pos1 = op.pos2;
+        replace2.text = textAt(op.pos1, op.length);
+        replaceList->append(replace2);
+        break;
+
+    case EditOp::Insert:
+        replace1.pos1 = op.pos1;
+        replace1.text = op.text;
+        replaceList->append(replace1);
+        break;
 
-    Replace cut;
-    cut.pos = move.pos;
-    cut.length = move.length;
-    Replace paste;
-    paste.pos = move.to;
-    paste.length = 0;
-    paste.replacement = text;
-
-    m_replaceList.append(cut);
-    m_replaceList.append(paste);
-
-    Replace cmd;
-    while (!m_replaceList.isEmpty()) {
-        cmd = m_replaceList.first();
-        m_replaceList.removeFirst();
-        doReplace(cmd);
+    case EditOp::Remove:
+        replace1.pos1 = op.pos1;
+        replace1.length = op.length;
+        replaceList->append(replace1);
+        break;
+
+    case EditOp::Flip:
+        replace1.pos1 = op.pos1;
+        replace1.length = op.length;
+        replace1.text = textAt(op.pos2, op.length);
+        replaceList->append(replace1);
+
+        replace2.pos1 = op.pos2;
+        replace2.length = op.length;
+        replace2.text = textAt(op.pos1, op.length);
+        replaceList->append(replace2);
+        break;
+
+    case EditOp::Copy:
+        replace1.pos1 = op.pos2;
+        replace1.text = textAt(op.pos1, op.length);
+        replaceList->append(replace1);
+        break;
+
+    case EditOp::Unset:
+        break;
     }
 }
 
-void ChangeSet::write(QString *s)
+bool ChangeSet::hadErrors()
+{
+    return m_error;
+}
+
+void ChangeSet::apply(QString *s)
 {
     m_string = s;
-    write_helper();
+    apply_helper();
     m_string = 0;
 }
 
-void ChangeSet::write(QTextCursor *textCursor)
+void ChangeSet::apply(QTextCursor *textCursor)
 {
     m_cursor = textCursor;
-    write_helper();
+    apply_helper();
     m_cursor = 0;
 }
 
-void ChangeSet::write_helper()
+QString ChangeSet::textAt(int pos, int length)
 {
-    if (m_cursor)
-        m_cursor->beginEditBlock();
-    {
-        Replace cmd;
-        while (!m_replaceList.isEmpty()) {
-            cmd = m_replaceList.first();
-            m_replaceList.removeFirst();
-            doReplace(cmd);
-        }
+    if (m_string) {
+        return m_string->mid(pos, length);
+    } else if (m_cursor) {
+        m_cursor->setPosition(pos);
+        m_cursor->setPosition(pos + length, QTextCursor::KeepAnchor);
+        return m_cursor->selectedText();
     }
+    return QString();
+}
+
+void ChangeSet::apply_helper()
+{
+    // convert all ops to replace
+    QList<EditOp> replaceList;
     {
-        Move cmd;
-        while (!m_moveList.isEmpty()) {
-            cmd = m_moveList.first();
-            m_moveList.removeFirst();
-            doMove(cmd);
+        while (!m_operationList.isEmpty()) {
+            const EditOp cmd(m_operationList.first());
+            m_operationList.removeFirst();
+            convertToReplace(cmd, &replaceList);
         }
     }
+
+    // execute replaces
+    if (m_cursor)
+        m_cursor->beginEditBlock();
+
+    while (!replaceList.isEmpty()) {
+        const EditOp cmd(replaceList.first());
+        replaceList.removeFirst();
+        doReplace(cmd, &replaceList);
+    }
+
     if (m_cursor)
         m_cursor->endEditBlock();
 }
diff --git a/src/libs/utils/changeset.h b/src/libs/utils/changeset.h
index c68efcdb6e024d7f038f60a007761032724d9ba0..7f953b402a9e5933e7b996dda51c3f47016d2801 100644
--- a/src/libs/utils/changeset.h
+++ b/src/libs/utils/changeset.h
@@ -46,6 +46,7 @@
 
 #include <QtCore/QString>
 #include <QtCore/QList>
+#include <QtCore/QSharedPointer>
 #include <QtGui/QTextCursor>
 
 namespace Utils {
@@ -53,20 +54,26 @@ namespace Utils {
 class QTCREATOR_UTILS_EXPORT ChangeSet
 {
 public:
-    struct Replace {
-        Replace(): pos(0), length(0) {}
-
-        int pos;
+    struct EditOp {
+        enum Type
+        {
+            Unset,
+            Replace,
+            Move,
+            Insert,
+            Remove,
+            Flip,
+            Copy
+        };
+
+        EditOp(): type(Unset), pos1(0), pos2(0), length(0) {}
+        EditOp(Type t): type(t), pos1(0), pos2(0), length(0) {}
+
+        Type type;
+        int pos1;
+        int pos2;
         int length;
-        QString replacement;
-    };
-
-    struct Move {
-        Move(): pos(0), length(0), to(0) {}
-
-        int pos;
-        int length;
-        int to;
+        QString text;
     };
 
 public:
@@ -74,31 +81,37 @@ public:
 
     bool isEmpty() const;
 
-    QList<Replace> replaceList() const;
-    QList<Move> moveList() const; // ### TODO: merge with replaceList
+    QList<EditOp> operationList() const;
 
     void clear();
 
-    void replace(int pos, int length, const QString &replacement);
-    void move(int pos, int length, int to);
+    bool replace(int pos, int length, const QString &replacement);
+    bool move(int pos, int length, int to);
+    bool insert(int pos, const QString &text);
+    bool remove(int pos, int length);
+    bool flip(int pos1, int length, int pos2);
+    bool copy(int pos, int length, int to);
+
+    bool hadErrors();
 
-    void write(QString *s);
-    void write(QTextCursor *textCursor);
+    void apply(QString *s);
+    void apply(QTextCursor *textCursor);
 
 private:
     bool hasOverlap(int pos, int length);
-    bool hasMoveInto(int pos, int length);
+    QString textAt(int pos, int length);
 
-    void doReplace(const Replace &replace);
-    void doMove(const Move &move);
+    void doReplace(const EditOp &replace, QList<EditOp> *replaceList);
+    void convertToReplace(const EditOp &op, QList<EditOp> *replaceList);
 
-    void write_helper();
+    void apply_helper();
 
 private:
     QString *m_string;
     QTextCursor *m_cursor;
-    QList<Replace> m_replaceList;
-    QList<Move> m_moveList;
+
+    QList<EditOp> m_operationList;
+    bool m_error;
 };
 
 } // namespace Utils
diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp
index 2240d67e680dcbaa6a80bb63d7aa759e5ef6f902..31420bcff041e747be1cab37b85e6854a9024cd9 100644
--- a/src/plugins/cppeditor/cppquickfix.cpp
+++ b/src/plugins/cppeditor/cppquickfix.cpp
@@ -769,7 +769,7 @@ void QuickFixOperation::apply()
 
     _textCursor.beginEditBlock();
 
-    _changeSet.write(&_textCursor);
+    _changeSet.apply(&_textCursor);
 
     if (_topLevelNode)
         reindent(range);
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index 193c1156ecb46e4197f8154f418e6b1d6eab36f7..28111c1bd38582e1ecc122d970ca16c9eff4d390 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -5,4 +5,5 @@ SUBDIRS += \
     debugger \
     fakevim \
 #    profilereader \
-    aggregation
+    aggregation \
+    changeset
diff --git a/tests/auto/changeset/changeset.pro b/tests/auto/changeset/changeset.pro
new file mode 100644
index 0000000000000000000000000000000000000000..5aa2358a65b7fd4ad0fa50488d254d86a47fdf3a
--- /dev/null
+++ b/tests/auto/changeset/changeset.pro
@@ -0,0 +1,18 @@
+
+QT += testlib
+
+# Defines import symbol as empty
+DEFINES+=QTCREATOR_UTILS_STATIC_LIB
+
+UTILSDIR = ../../../src/libs
+
+SOURCES += \
+	tst_changeset.cpp \
+	$$UTILSDIR/utils/changeset.cpp
+
+HEADERS += \
+	$$UTILSDIR/utils/changeset.h
+
+INCLUDEPATH += $$UTILSDIR
+
+TARGET=tst_$$TARGET
diff --git a/tests/auto/changeset/tst_changeset.cpp b/tests/auto/changeset/tst_changeset.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..89e7be59d8467dae33eb72806bd9a6faaac2e1b6
--- /dev/null
+++ b/tests/auto/changeset/tst_changeset.cpp
@@ -0,0 +1,426 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include <utils/changeset.h>
+
+#include <QtTest/QtTest>
+
+class tst_ChangeSet : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void singleReplace();
+    void singleMove();
+    void singleInsert();
+    void singleRemove();
+    void singleFlip();
+    void singleCopy();
+
+    void doubleInsert();
+    void conflicts();
+};
+
+
+void tst_ChangeSet::singleReplace()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.replace(0, 2, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("ghicdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.replace(4, 2, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdghi"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.replace(3, 0, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcghidef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.replace(0, 6, ""));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String(""));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.replace(3, 10, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcghi"));
+    }
+}
+
+void tst_ChangeSet::singleMove()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.move(0, 2, 4));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("cdabef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.move(4, 2, 0));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("efabcd"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.move(3, 10, 0));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("defabc"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.move(3, 0, 0));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.move(0, 1, 10));
+        cs.apply(&test);
+        // ### maybe this should expand the string or error?
+        QCOMPARE(test, QLatin1String("bcdef"));
+    }
+}
+
+void tst_ChangeSet::singleInsert()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.insert(0, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("ghiabcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.insert(6, "ghi"));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdefghi"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.insert(3, ""));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.insert(7, "g"));
+        cs.apply(&test);
+        // ### maybe this should expand the string or error?
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+}
+
+void tst_ChangeSet::singleRemove()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.remove(0, 1));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("bcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.remove(3, 3));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abc"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.remove(4, 10));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcd"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.remove(2, 0));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.remove(7, 1));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+}
+
+void tst_ChangeSet::singleFlip()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.flip(0, 2, 4));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("efcdab"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.flip(1, 2, 3));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("adebcf"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.flip(3, 0, 4));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.flip(0, 6, 6));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.flip(0, 6, 7));
+        cs.apply(&test);
+        // ### maybe this should expand the string or error?
+        QCOMPARE(test, QLatin1String(""));
+    }
+    {
+        Utils::ChangeSet cs;
+        QCOMPARE(cs.flip(0, 3, 1), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QCOMPARE(cs.flip(0, 3, 2), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.flip(3, 3, 0));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("defabc"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.flip(0, 3, 3));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("defabc"));
+    }
+}
+
+void tst_ChangeSet::singleCopy()
+{
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.copy(0, 2, 4));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdabef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.copy(1, 2, 3));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcbcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.copy(3, 0, 4));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.copy(0, 6, 6));
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcdefabcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QString test("abcdef");
+        QVERIFY(cs.copy(0, 6, 7));
+        cs.apply(&test);
+        // ### maybe this should expand the string or error?
+        QCOMPARE(test, QLatin1String("abcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QCOMPARE(cs.copy(0, 3, 1), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QCOMPARE(cs.copy(0, 3, 2), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.copy(0, 3, 0));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcabcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.copy(0, 3, 3));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("abcabcdef"));
+    }
+}
+
+void tst_ChangeSet::doubleInsert()
+{
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.insert(1, "01"));
+        QVERIFY(cs.insert(1, "234"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("a01234bcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.insert(1, "234"));
+        QVERIFY(cs.insert(1, "01"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("a23401bcdef"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.insert(1, "01"));
+        QVERIFY(cs.remove(1, 1));
+        QVERIFY(cs.insert(2, "234"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("a01234cdef"));
+    }
+}
+
+void tst_ChangeSet::conflicts()
+{
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(0, 2, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(1, 3, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(1, 1, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(2, 0, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(2, 1, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(3, 0, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(3, 1, "abc"), false);
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QCOMPARE(cs.replace(4, 2, "abc"), false);
+    }
+
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QVERIFY(cs.replace(0, 1, "bla"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("blaebcdf"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QVERIFY(cs.replace(4, 1, "bla"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("ablabcdf"));
+    }
+    {
+        Utils::ChangeSet cs;
+        QVERIFY(cs.move(1, 3, 5));
+        QVERIFY(cs.replace(5, 1, "bla"));
+        QString test("abcdef");
+        cs.apply(&test);
+        QCOMPARE(test, QLatin1String("aebcdbla"));
+    }
+}
+
+QTEST_MAIN(tst_ChangeSet)
+
+#include "tst_changeset.moc"