diff --git a/src/plugins/duieditor/duieditor.pro b/src/plugins/duieditor/duieditor.pro
index 4af552b625bcbb3aa61fdd9a8b771a5c2a3c5cd5..9149293c5164ecae1e1ae25d6a08521a2f98e8d7 100644
--- a/src/plugins/duieditor/duieditor.pro
+++ b/src/plugins/duieditor/duieditor.pro
@@ -7,10 +7,12 @@ include(../../plugins/coreplugin/coreplugin.pri)
 include(../../plugins/texteditor/texteditor.pri)
 include(../../shared/qscripthighlighter/qscripthighlighter.pri)
 include(../../shared/indenter/indenter.pri)
-include($$(QTDIR_DUI)/src/declarative/qml/parser/parser.pri)
 
+include($$(QTDIR_DUI)/src/declarative/qml/parser/parser.pri)
 INCLUDEPATH += $$(QTDIR_DUI)/src/declarative/qml    # FIXME: remove me
 
+include(rewriter/rewriter.pri)
+
 HEADERS += duieditor.h \
 duieditorfactory.h \
 duieditorplugin.h \
diff --git a/src/plugins/duieditor/rewriter/rewriter.cpp b/src/plugins/duieditor/rewriter/rewriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..81f471fbb97ae2da043b0ab3b431ef62aba00bb5
--- /dev/null
+++ b/src/plugins/duieditor/rewriter/rewriter.cpp
@@ -0,0 +1,52 @@
+#include "rewriter.h"
+#include "parser/javascriptast_p.h"
+
+using namespace JavaScript;
+
+void Rewriter::replace(const AST::SourceLocation &loc, const QString &text)
+{ replace(loc.offset, loc.length, text); }
+
+void Rewriter::remove(const AST::SourceLocation &loc)
+{ return replace(loc.offset, loc.length, QString()); }
+
+void Rewriter::remove(const AST::SourceLocation &firstLoc, const AST::SourceLocation &lastLoc)
+{ return replace(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, QString()); }
+
+void Rewriter::insertTextBefore(const AST::SourceLocation &loc, const QString &text)
+{ replace(loc.offset, 0, text); }
+
+void Rewriter::insertTextAfter(const AST::SourceLocation &loc, const QString &text)
+{ replace(loc.offset + loc.length, 0, text); }
+
+void Rewriter::replace(int offset, int length, const QString &text)
+{ textWriter.replace(offset, length, text); }
+
+void Rewriter::insertText(int offset, const QString &text)
+{ replace(offset, 0, text); }
+
+void Rewriter::removeText(int offset, int length)
+{ replace(offset, length, QString()); }
+
+QString Rewriter::textAt(const AST::SourceLocation &loc) const
+{ return _code.mid(loc.offset, loc.length); }
+
+QString Rewriter::textAt(const AST::SourceLocation &firstLoc, const AST::SourceLocation &lastLoc) const
+{ return _code.mid(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset); }
+
+void Rewriter::accept(JavaScript::AST::Node *node)
+{ JavaScript::AST::Node::acceptChild(node, this); }
+
+void Rewriter::moveTextBefore(const AST::SourceLocation &firstLoc,
+                              const AST::SourceLocation &lastLoc,
+                              const AST::SourceLocation &loc)
+{
+    textWriter.move(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, loc.offset);
+}
+
+void Rewriter::moveTextAfter(const AST::SourceLocation &firstLoc,
+                             const AST::SourceLocation &lastLoc,
+                             const AST::SourceLocation &loc)
+{
+    textWriter.move(firstLoc.offset, lastLoc.offset + lastLoc.length - firstLoc.offset, loc.offset + loc.length);
+}
+
diff --git a/src/plugins/duieditor/rewriter/rewriter.h b/src/plugins/duieditor/rewriter/rewriter.h
new file mode 100644
index 0000000000000000000000000000000000000000..03d4731b336db333bec6571358d1bda62a9ab171
--- /dev/null
+++ b/src/plugins/duieditor/rewriter/rewriter.h
@@ -0,0 +1,100 @@
+#ifndef REWRITER_H
+#define REWRITER_H
+
+#include <QList>
+#include <QString>
+
+#include "textwriter.h"
+#include "parser/javascriptastvisitor_p.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Replacement
+////////////////////////////////////////////////////////////////////////////////
+class Replacement
+{
+    int _offset;
+    int _length;
+    QString _text;
+
+public:
+    Replacement(int offset = 0, int length = 0, const QString &text = QString())
+        : _offset(offset), _length(length), _text(text)
+    { }
+
+    bool isNull() const { return _offset == _length; }
+    operator bool() const { return ! isNull(); }
+
+    int offset() const { return _offset; }
+    int length() const { return _length; }
+    QString text() const { return _text; }
+};
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Rewriter
+////////////////////////////////////////////////////////////////////////////////
+class Rewriter: public JavaScript::AST::Visitor
+{
+protected:
+    TextWriter textWriter;
+public:
+    //
+    // Token based API
+    //
+
+    /// Returns the text of the token at the given \a location.
+    QString textAt(const JavaScript::AST::SourceLocation &location) const;
+
+    QString textAt(const JavaScript::AST::SourceLocation &firstLoc,
+                   const JavaScript::AST::SourceLocation &lastLoc) const;
+
+    /// Replace the token at \a loc with the given \a text.
+    void replace(const JavaScript::AST::SourceLocation &loc, const QString &text);
+
+    /// Remove the token at the given \a location.
+    void remove(const JavaScript::AST::SourceLocation &location);
+
+    /// Remove all tokens in the range [\a firstLoc, \a lastLoc].
+    void remove(const JavaScript::AST::SourceLocation &firstLoc, const JavaScript::AST::SourceLocation &lastLoc);
+
+    /// Insert \a text before the token at the given \a location.
+    void insertTextBefore(const JavaScript::AST::SourceLocation &location, const QString &text);
+
+    /// Insert \a text after the token at the given \a location.
+    void insertTextAfter(const JavaScript::AST::SourceLocation &loc, const QString &text);
+
+    void moveTextBefore(const JavaScript::AST::SourceLocation &firstLoc,
+                        const JavaScript::AST::SourceLocation &lastLoc,
+                        const JavaScript::AST::SourceLocation &loc);
+
+    void moveTextAfter(const JavaScript::AST::SourceLocation &firstLoc,
+                       const JavaScript::AST::SourceLocation &lastLoc,
+                       const JavaScript::AST::SourceLocation &loc);
+
+    //
+    // low-level offset based API
+    //
+    void replace(int offset, int length, const QString &text);
+    void insertText(int offset, const QString &text);
+    void removeText(int offset, int length);
+
+    /// Visit the given \a node.
+    void accept(JavaScript::AST::Node *node);
+
+    /// Returns the original unchanged source code.
+    QString code() const { return _code; }
+
+    /// Returns the list of replacements.
+    QList<Replacement> replacementList() const { return _replacementList; }
+
+protected:
+    /// \internal
+    void setCode(const QString &code) { _code = code; }
+
+private:
+    QString _code;
+    QList<Replacement> _replacementList;
+};
+
+#endif // REWRITER_H
diff --git a/src/plugins/duieditor/rewriter/rewriter.pri b/src/plugins/duieditor/rewriter/rewriter.pri
new file mode 100644
index 0000000000000000000000000000000000000000..2cf55b64a28a7436015a03a37bc21eab73862e6d
--- /dev/null
+++ b/src/plugins/duieditor/rewriter/rewriter.pri
@@ -0,0 +1,2 @@
+HEADERS += $$PWD/rewriter.h $$PWD/textwriter.h
+SOURCES += $$PWD/rewriter.cpp $$PWD/textwriter.cpp
diff --git a/src/plugins/duieditor/rewriter/textwriter.cpp b/src/plugins/duieditor/rewriter/textwriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cd61138bef73c5420ea9b7d364037bbfa722a1b1
--- /dev/null
+++ b/src/plugins/duieditor/rewriter/textwriter.cpp
@@ -0,0 +1,170 @@
+#include "textwriter.h"
+
+TextWriter::TextWriter()
+        :string(0), cursor(0)
+{
+}
+
+static bool overlaps(int posA, int lengthA, int posB, int lengthB) {
+    return (posA < posB + lengthB && posA + lengthA > posB + lengthB)
+            || (posA < posB && posA + lengthA > posB);
+}
+
+bool TextWriter::hasOverlap(int pos, int length)
+{
+    {
+        QListIterator<Replace> i(replaceList);
+        while (i.hasNext()) {
+            const Replace &cmd = i.next();
+            if (overlaps(pos, length, cmd.pos, cmd.length))
+                return true;
+        }
+    }
+    {
+        QListIterator<Move> i(moveList);
+        while (i.hasNext()) {
+            const Move &cmd = i.next();
+            if (overlaps(pos, length, cmd.pos, cmd.length))
+                return true;
+        }
+        return false;
+    }
+}
+
+bool TextWriter::hasMoveInto(int pos, int length)
+{
+    QListIterator<Move> i(moveList);
+    while (i.hasNext()) {
+        const Move &cmd = i.next();
+        if (cmd.to >= pos && cmd.to < pos + length)
+            return true;
+    }
+    return false;
+}
+
+void TextWriter::replace(int pos, int length, const QString &replacement)
+{
+    Q_ASSERT(!hasOverlap(pos, length));
+    Q_ASSERT(!hasMoveInto(pos, length));
+
+    Replace cmd;
+    cmd.pos = pos;
+    cmd.length = length;
+    cmd.replacement = replacement;
+    replaceList += cmd;
+}
+
+void TextWriter::move(int pos, int length, int to)
+{
+    Q_ASSERT(!hasOverlap(pos, length));
+
+    Move cmd;
+    cmd.pos = pos;
+    cmd.length = length;
+    cmd.to = to;
+    moveList += cmd;
+}
+
+void TextWriter::doReplace(const Replace &replace)
+{
+    int diff = replace.replacement.size() - replace.length;
+    {
+        QMutableListIterator<Replace> i(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;
+        }
+    }
+    {
+        QMutableListIterator<Move> i(moveList);
+        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;
+        }
+    }
+
+    if (string) {
+        string->replace(replace.pos, replace.length, replace.replacement);
+    } else if (cursor) {
+        cursor->setPosition(replace.pos);
+        cursor->setPosition(replace.pos + replace.length, QTextCursor::KeepAnchor);
+        cursor->insertText(replace.replacement);
+    }
+}
+
+void TextWriter::doMove(const Move &move)
+{
+    QString text;
+    if (string) {
+        text = string->mid(move.pos, move.length);
+    } else if (cursor) {
+        cursor->setPosition(move.pos);
+        cursor->setPosition(move.pos + move.length, QTextCursor::KeepAnchor);
+        text = cursor->selectedText();
+    }
+
+    Replace cut;
+    cut.pos = move.pos;
+    cut.length = move.length;
+    Replace paste;
+    paste.pos = move.to;
+    paste.length = 0;
+    paste.replacement = text;
+
+    replaceList.append(cut);
+    replaceList.append(paste);
+
+    Replace cmd;
+    while (!replaceList.isEmpty()) {
+        cmd = replaceList.first();
+        replaceList.removeFirst();
+        doReplace(cmd);
+    }
+}
+
+void TextWriter::write(QString *s)
+{
+    string = s;
+    write_helper();
+    string = 0;
+}
+
+void TextWriter::write(QTextCursor *textCursor)
+{
+    cursor = textCursor;
+    write_helper();
+    cursor = 0;
+}
+
+void TextWriter::write_helper()
+{
+    if (cursor)
+        cursor->beginEditBlock();
+    {
+        Replace cmd;
+        while (!replaceList.isEmpty()) {
+            cmd = replaceList.first();
+            replaceList.removeFirst();
+            doReplace(cmd);
+        }
+    }
+    {
+        Move cmd;
+        while (!moveList.isEmpty()) {
+            cmd = moveList.first();
+            moveList.removeFirst();
+            doMove(cmd);
+        }
+    }
+    if (cursor)
+        cursor->endEditBlock();
+}
diff --git a/src/plugins/duieditor/rewriter/textwriter.h b/src/plugins/duieditor/rewriter/textwriter.h
new file mode 100644
index 0000000000000000000000000000000000000000..dcd3a5f472b6e47d4ebe895c167f54f1609b98f4
--- /dev/null
+++ b/src/plugins/duieditor/rewriter/textwriter.h
@@ -0,0 +1,49 @@
+#ifndef TEXTWRITER_H
+#define TEXTWRITER_H
+
+#include <QString>
+#include <QList>
+#include <QTextCursor>
+
+
+class TextWriter
+{
+    QString *string;
+    QTextCursor *cursor;
+
+    struct Replace {
+        int pos;
+        int length;
+        QString replacement;
+    };
+
+    QList<Replace> replaceList;
+
+    struct Move {
+        int pos;
+        int length;
+        int to;
+    };
+
+    QList<Move> moveList;
+
+    bool hasOverlap(int pos, int length);
+    bool hasMoveInto(int pos, int length);
+
+    void doReplace(const Replace &replace);
+    void doMove(const Move &move);
+
+    void write_helper();
+
+public:
+    TextWriter();
+
+    void replace(int pos, int length, const QString &replacement);
+    void move(int pos, int length, int to);
+
+    void write(QString *s);
+    void write(QTextCursor *textCursor);
+
+};
+
+#endif // TEXTWRITER_H