diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp
index 24f33aeded939a3d499c745537fc574e69cb97ff..cf9245735a6db2e57b470776049c5c169424fe55 100644
--- a/src/plugins/fakevim/fakevim_test.cpp
+++ b/src/plugins/fakevim/fakevim_test.cpp
@@ -218,6 +218,14 @@ struct FakeVimPlugin::TestData
         setPosition(i);
     }
 
+    // Simulate text completion by inserting text directly to editor widget (bypassing FakeVim).
+    void completeText(const QString &text)
+    {
+        QTextCursor tc = editor()->textCursor();
+        tc.insertText(text);
+        editor()->setTextCursor(tc);
+    }
+
     int lines() const
     {
         QTextDocument *doc = editor()->document();
@@ -476,6 +484,28 @@ void FakeVimPlugin::test_vim_insert()
     data.setText("abc" N "def");
     KEYS("<insert>XYZ<insert>xyz<esc>", "XYZxy" X "z" N "def");
     KEYS("<insert><insert>" "<c-o>0<c-o>j" "XY<insert>Z", "XYZxyz" N "XYZ" X "f");
+
+    // dot command for insert
+    data.setText("abc" N "def");
+    KEYS("ix<insert>X<insert>y<esc>", "xX" X "ybc" N "def");
+    KEYS("0j.", "xXybc" N "xX" X "yef");
+
+    data.setText("abc" N "def");
+    KEYS("<insert>x<insert>X<right>Y<esc>", "xXb" X "Y" N "def");
+    KEYS("0j.", "xXbY" N X "Yef");
+
+    data.setText("abc" N "def");
+    KEYS("<insert>x<insert>X<left><left><down><esc>", "xXbc" N X "def");
+    KEYS(".", "xXbc" N "x" X "Xef");
+
+    // delete in insert mode is part of dot command
+    data.setText("abc" N "def");
+    KEYS("iX<delete>Y", "XY" X "bc" N "def");
+    KEYS("0j.", "XYbc" N "X" X "Yef");
+
+    data.setText("abc" N "def");
+    KEYS("2iX<delete>Y<esc>", "XYX" X "Yc" N "def");
+    KEYS("0j.", "XYXYc" N "XYX" X "Yf");
 }
 
 void FakeVimPlugin::test_vim_fFtT()
@@ -1548,6 +1578,58 @@ void FakeVimPlugin::test_vim_code_folding()
     // Opening folds recursively isn't supported (previous position in fold isn't restored).
 }
 
+void FakeVimPlugin::test_vim_code_completion()
+{
+    // Test completion by simply bypassing FakeVim and inserting text directly in editor widget.
+    TestData data;
+    setup(&data);
+    data.setText(
+        "int test1Var;" N
+        "int test2Var;" N
+        "int main() {" N
+        "    " X ";" N
+        "}" N
+        "");
+
+    data.doKeys("i" "te");
+    data.completeText("st");
+    data.doKeys("1");
+    data.completeText("Var");
+    data.doKeys(" = 0");
+    KEYS("",
+        "int test1Var;" N
+        "int test2Var;" N
+        "int main() {" N
+        "    test1Var = " X "0;" N
+        "}" N
+        "");
+
+    data.doKeys("o" "te");
+    data.completeText("st");
+    data.doKeys("2");
+    data.completeText("Var");
+    data.doKeys(" = 1;");
+    KEYS("",
+        "int test1Var;" N
+        "int test2Var;" N
+        "int main() {" N
+        "    test1Var = 0;" N
+        "    test2Var = 1" X ";" N
+        "}" N
+        "");
+
+    // repeat text insertion with completion
+    KEYS(".",
+        "int test1Var;" N
+        "int test2Var;" N
+        "int main() {" N
+        "    test1Var = 0;" N
+        "    test2Var = 1;" N
+        "    test2Var = 1" X ";" N
+        "}" N
+        "");
+}
+
 void FakeVimPlugin::test_vim_substitute()
 {
     TestData data;
diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp
index b2d7a1683469987caf4f41fd1509603ea376ae7f..ea64a34ae9244ada68f2f3145eb8d0771b0b7991 100644
--- a/src/plugins/fakevim/fakevimhandler.cpp
+++ b/src/plugins/fakevim/fakevimhandler.cpp
@@ -672,6 +672,8 @@ static const QMap<QString, int> &vimKeyNames()
     k.insert("LEFT", Key_Left);
     k.insert("RIGHT", Key_Right);
 
+    k.insert("LT", Key_Less);
+
     k.insert("F1", Key_F1);
     k.insert("F2", Key_F2);
     k.insert("F3", Key_F3);
@@ -1578,6 +1580,7 @@ public:
 
     Q_SLOT void importSelection();
     void exportSelection();
+    void recordInsertion(const QString &insert = QString());
     void ensureCursorVisible();
     void insertInInsertMode(const QString &text);
 
@@ -1615,7 +1618,6 @@ public:
 
     int m_findStartPosition;
     QString m_lastInsertion;
-    QString m_lastDeletion;
 
     bool m_breakEditBlock;
 
@@ -1867,7 +1869,7 @@ void FakeVimHandler::Private::init()
 void FakeVimHandler::Private::focus()
 {
     stopIncrementalFind();
-    if (g.returnToMode != CommandMode && g.currentCommand.isEmpty() && m_mode != ExMode) {
+    if (m_mode == CommandMode && g.returnToMode != CommandMode && g.currentCommand.isEmpty()) {
         // Return to insert mode.
         resetCommandMode();
         updateMiniBuffer();
@@ -1962,18 +1964,8 @@ EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
     // Position changed externally, e.g. by code completion.
     if (position() != m_oldPosition) {
         setTargetColumn();
-        //qDebug() << "POSITION CHANGED EXTERNALLY";
-        if (m_mode == InsertMode) {
-            int dist = position() - m_oldPosition;
-            // Try to compensate for code completion
-            if (dist > 0 && dist <= physicalCursorColumn()) {
-                Range range(m_oldPosition, position());
-                m_lastInsertion.append(selectText(range));
-            }
-        } else if (!isVisualMode()) {
-            if (atEndOfLine())
-                moveLeft();
-        }
+        if (atEndOfLine() && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode)
+            moveLeft();
     }
 
     QTextCursor tc = cursor();
@@ -2095,6 +2087,29 @@ void FakeVimHandler::Private::exportSelection()
     m_oldExternalAnchor = anchor();
 }
 
+void FakeVimHandler::Private::recordInsertion(const QString &insert)
+{
+    const int pos = position();
+
+    if (insert.isNull()) {
+        const int dist = pos - m_oldPosition;
+
+        if (dist > 0) {
+            Range range(m_oldPosition, pos);
+            QString text = selectText(range);
+            // escape text like <ESC>
+            text.replace("<", "<LT>");
+            m_lastInsertion.append(text);
+        } else if (dist < 0) {
+            m_lastInsertion.resize(m_lastInsertion.size() + dist);
+        }
+    } else {
+        m_lastInsertion += insert;
+    }
+
+    m_oldPosition = position();
+}
+
 void FakeVimHandler::Private::ensureCursorVisible()
 {
     int pos = position();
@@ -2628,6 +2643,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
             insertAutomaticIndentation(true);
         endEditBlock();
         setTargetColumn();
+        m_lastInsertion.clear();
         g.returnToMode = InsertMode;
     } else if (m_submode == DeleteSubMode) {
         setUndoPosition();
@@ -2712,16 +2728,18 @@ void FakeVimHandler::Private::resetCommandMode()
     m_register = '"';
     //m_tc.clearSelection();
     m_rangemode = RangeCharMode;
-    if (isNoVisualMode())
-        setAnchor();
     g.currentCommand.clear();
     if (g.returnToMode != CommandMode) {
+        const QString lastInsertion = m_lastInsertion;
         if (g.returnToMode == InsertMode)
             enterInsertMode();
         else
             enterReplaceMode();
+        m_lastInsertion = lastInsertion;
         moveToTargetColumn();
     }
+    if (isNoVisualMode())
+        setAnchor();
 }
 
 void FakeVimHandler::Private::updateSelection()
@@ -3383,11 +3401,10 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
     } else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) {
         leaveVisualMode();
         breakEditBlock();
-        enterInsertMode();
         setDotCommand("%1a", count());
-        m_lastInsertion.clear();
-        if (!atEndOfLine())
+        if (!atEndOfLine() && !atEmptyLine())
             moveRight();
+        enterInsertMode();
         setUndoPosition();
     } else if (input.is('A')) {
         breakEditBlock();
@@ -3396,7 +3413,6 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
         setAnchor();
         enterInsertMode();
         setDotCommand("%1A", count());
-        m_lastInsertion.clear();
     } else if (input.isControl('a')) {
         changeNumberTextObject(count());
         setDotCommand("%1<c-a>", count());
@@ -3548,10 +3564,10 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
         }
         beginEditBlock();
         insertText(QString("\n"));
-        m_lastInsertion += '\n';
         if (!appendLine)
             moveUp();
         insertAutomaticIndentation(insertAfter);
+        recordInsertion(QString("\n"));
         setTargetColumn();
         endEditBlock();
     } else if (input.isControl('o')) {
@@ -3758,7 +3774,6 @@ bool FakeVimHandler::Private::handleChangeDeleteSubModes(const Input &input)
         const int pos = lastPositionInLine(line + count() - 1);
         setAnchorAndPosition(anc, pos);
         if (m_submode == ChangeSubMode) {
-            m_lastInsertion.clear();
             setDotCommand("%1cc", count());
         } else {
             setDotCommand("%1dd", count());
@@ -3967,44 +3982,54 @@ bool FakeVimHandler::Private::handleCapitalZSubMode(const Input &input)
 
 EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
 {
+    bool clearLastInsertion = m_breakEditBlock;
+    if (m_oldPosition != position()) {
+        if (clearLastInsertion) {
+            clearLastInsertion = false;
+            m_lastInsertion = "<INSERT>";
+        }
+        recordInsertion();
+    }
+
     if (input.isEscape()) {
         moveLeft(qMin(1, leftDist()));
-        setTargetColumn();
         enterCommandMode();
+        g.dotCommand += m_lastInsertion;
+        g.dotCommand += QChar(27);
     } else if (input.isKey(Key_Left)) {
         breakEditBlock();
         moveLeft(1);
-        setTargetColumn();
     } else if (input.isKey(Key_Right)) {
         breakEditBlock();
         moveRight(1);
-        setTargetColumn();
     } else if (input.isKey(Key_Up)) {
         breakEditBlock();
         moveUp(1);
-        setTargetColumn();
     } else if (input.isKey(Key_Down)) {
         breakEditBlock();
         moveDown(1);
     } else if (input.isKey(Key_Insert)) {
         m_mode = InsertMode;
+        recordInsertion("<INSERT>");
     } else if (input.isControl('o')) {
         enterCommandMode(ReplaceMode);
     } else {
+        if (clearLastInsertion)
+            m_lastInsertion = "<INSERT>";
         joinPreviousEditBlock();
         if (!atEndOfLine()) {
             setAnchor();
             moveRight();
-            m_lastDeletion += selectText(Range(position(), anchor()));
             removeText(currentRange());
         }
         const QString text = input.text();
-        m_lastInsertion += text;
         setAnchor();
         insertText(text);
         endEditBlock();
-        setTargetColumn();
+        recordInsertion();
     }
+    m_oldPosition = position();
+    setTargetColumn();
     updateMiniBuffer();
 
     return EventHandled;
@@ -4012,9 +4037,17 @@ EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
 
 EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
 {
-    //const int key = input.key;
-    //const QString &text = input.text;
+    bool clearLastInsertion = m_breakEditBlock;
+    if (m_oldPosition != position()) {
+        if (clearLastInsertion) {
+            clearLastInsertion = false;
+            m_lastInsertion.clear();
+        }
+        recordInsertion();
+    }
 
+    QString insert;
+    bool move = false;
     if (input.isEscape()) {
         if (isVisualBlockMode() && !m_lastInsertion.contains('\n')) {
             leaveVisualMode();
@@ -4022,7 +4055,6 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
             moveLeft(m_lastInsertion.size());
             setAnchor();
             int pos = position();
-            setTargetColumn();
             for (int i = 0; i < m_visualInsertCount; ++i) {
                 moveDown();
                 insertText(m_lastInsertion);
@@ -4039,23 +4071,19 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
             const int repeat = count();
             if (repeat > 1) {
                 const QString text = m_lastInsertion;
-                for (int i = 1; i < repeat; ++i) {
-                    m_lastInsertion.truncate(0);
-                    foreach (const QChar &c, text)
-                        handleInsertMode(Input(c));
-                }
+                m_lastInsertion.clear();
+                for (int i = 1; i < repeat; ++i)
+                    replay(text);
                 m_lastInsertion = text;
             }
             moveLeft(qMin(1, leftDist()));
-            setTargetColumn();
             leaveVisualMode();
             breakEditBlock();
         }
         // If command is 'o' or 'O' don't include the first line feed in dot command.
         if (g.dotCommand.endsWith(QChar('o'), Qt::CaseInsensitive))
             m_lastInsertion.remove(0, 1);
-        g.dotCommand += m_lastInsertion;
-        g.dotCommand += QChar(27);
+        g.dotCommand += m_lastInsertion + "<ESC>";
         enterCommandMode();
         m_ctrlVActive = false;
         m_opcount.clear();
@@ -4066,6 +4094,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
         enterCommandMode(InsertMode);
     } else if (input.isControl('v')) {
         m_ctrlVActive = true;
+        insert = "<C-V>";
     } else if (input.isControl('w')) {
         const int blockNumber = cursor().blockNumber();
         const int endPos = position();
@@ -4075,61 +4104,47 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
         const int beginPos = position();
         Range range(beginPos, endPos, RangeCharMode);
         removeText(range);
-        setTargetColumn();
+        insert = "<C-W>";
     } else if (input.isKey(Key_Insert)) {
         m_mode = ReplaceMode;
+        insert = "<INSERT>";
     } else if (input.isKey(Key_Left)) {
         moveLeft(count());
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isControl(Key_Left)) {
         moveToNextWordStart(count(), false, false);
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_Down)) {
         //removeAutomaticIndentation();
         m_submode = NoSubMode;
         moveDown(count());
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_Up)) {
         //removeAutomaticIndentation();
         m_submode = NoSubMode;
         moveUp(count());
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_Right)) {
         moveRight(count());
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isControl(Key_Right)) {
         moveToNextWordStart(count(), false, true);
         moveRight(); // we need one more move since we are in insert mode
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_Home)) {
         moveToStartOfLine();
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_End)) {
         if (count() > 1)
             moveDown(count() - 1);
         moveBehindEndOfLine();
-        setTargetColumn();
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) {
         joinPreviousEditBlock();
         m_submode = NoSubMode;
         insertText(QString("\n"));
-        m_lastInsertion += '\n';
+        insert = "\n";
         insertAutomaticIndentation(true);
-        setTargetColumn();
         endEditBlock();
     } else if (input.isBackspace()) {
         joinPreviousEditBlock();
@@ -4149,29 +4164,25 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
                 setLineContents(line, prefix + data.mid(col.physical));
                 moveToStartOfLine();
                 moveRight(prefix.size());
-                m_lastInsertion.clear(); // FIXME
             } else {
                 setAnchor();
                 cursor().deletePreviousChar();
-                m_lastInsertion.chop(1);
             }
-            setTargetColumn();
         }
+        insert = "<BS>";
         endEditBlock();
     } else if (input.isKey(Key_Delete)) {
         setAnchor();
         cursor().deleteChar();
-        m_lastInsertion.clear();
+        insert = "<DELETE>";
     } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
         removeAutomaticIndentation();
         moveDown(count() * (linesOnScreen() - 2));
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
         removeAutomaticIndentation();
         moveUp(count() * (linesOnScreen() - 2));
-        breakEditBlock();
-        m_lastInsertion.clear();
+        move = true;
     } else if (input.isKey(Key_Tab)) {
         m_justAutoIndented = 0;
         if (hasConfig(ConfigExpandTab)) {
@@ -4180,10 +4191,10 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
             QString str = QString(ts - col % ts, ' ');
             m_lastInsertion.append(str);
             insertText(str);
-            setTargetColumn();
         } else {
             insertInInsertMode(input.raw());
         }
+        insert = "\t";
     } else if (input.isControl('d')) {
         // remove one level of indentation from the current line
         int shift = config(ConfigShiftWidth).toInt();
@@ -4202,6 +4213,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
                 break;
         }
         removeText(Range(pos, pos+i));
+        insert = "<C-D>";
     //} else if (key >= control('a') && key <= control('z')) {
     //    // ignore these
     } else if (input.isControl('p') || input.isControl('n')) {
@@ -4210,13 +4222,29 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
         QString str = selectText(Range(position(), tc.position()));
         setCursor(tc);
         emit q->simpleCompletionRequested(str, input.isControl('n'));
+        if (input.isControl('p'))
+            insert = "<C-P>";
+        else
+            insert = "<C-N>";
     } else if (!input.text().isEmpty()) {
-        insertInInsertMode(input.text());
+        insert = input.text();
+        insertInInsertMode(insert);
     } else {
         // We don't want fancy stuff in insert mode.
         return EventHandled;
     }
+
+    if (move) {
+        breakEditBlock();
+        m_oldPosition = position();
+    } else {
+        if (clearLastInsertion)
+            m_lastInsertion.clear();
+        recordInsertion(insert);
+    }
+    setTargetColumn();
     updateMiniBuffer();
+
     return EventHandled;
 }
 
@@ -4224,7 +4252,6 @@ void FakeVimHandler::Private::insertInInsertMode(const QString &text)
 {
     joinPreviousEditBlock();
     m_justAutoIndented = 0;
-    m_lastInsertion.append(text);
     insertText(text);
     if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
         const QString leftText = block().text()
@@ -6570,7 +6597,7 @@ void FakeVimHandler::Private::enterReplaceMode()
     m_submode = NoSubMode;
     m_subsubmode = NoSubSubMode;
     m_lastInsertion.clear();
-    m_lastDeletion.clear();
+    m_oldPosition = position();
     g.returnToMode = ReplaceMode;
 }
 
@@ -6580,7 +6607,7 @@ void FakeVimHandler::Private::enterInsertMode()
     m_submode = NoSubMode;
     m_subsubmode = NoSubSubMode;
     m_lastInsertion.clear();
-    m_lastDeletion.clear();
+    m_oldPosition = position();
     g.returnToMode = InsertMode;
 }
 
diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h
index 8cd60b9b9844a4fad2782687a0927e12c02f78a0..58594727f842bda33c9a1f2270b097d47ac52489 100644
--- a/src/plugins/fakevim/fakevimplugin.h
+++ b/src/plugins/fakevim/fakevimplugin.h
@@ -80,6 +80,7 @@ private slots:
     void test_vim_letter_case();
     void test_vim_code_autoindent();
     void test_vim_code_folding();
+    void test_vim_code_completion();
     void test_vim_substitute();
     void test_vim_ex_yank();
     void test_vim_ex_delete();