From 8994bb2d4af2c38b55eee16fadb4801243fe95e3 Mon Sep 17 00:00:00 2001
From: Lukas Holecek <hluk@email.cz>
Date: Tue, 23 Oct 2012 16:35:13 +0200
Subject: [PATCH] fakevim: Added move and yank Ex commands

Change-Id: I4138a2035e338b665d91704b7c53117c4d924472
Reviewed-by: hjk <qthjk@ovi.com>
---
 src/plugins/fakevim/fakevim_test.cpp   |  43 ++++++
 src/plugins/fakevim/fakevimhandler.cpp | 179 ++++++++++++++++++-------
 src/plugins/fakevim/fakevimplugin.h    |   2 +
 3 files changed, 174 insertions(+), 50 deletions(-)

diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp
index 176448486ac..3c9455683fd 100644
--- a/src/plugins/fakevim/fakevim_test.cpp
+++ b/src/plugins/fakevim/fakevim_test.cpp
@@ -1321,6 +1321,49 @@ void FakeVimPlugin::test_vim_substitute()
     COMMAND("%s/\\( \\S \\)*//g", "abc" N "def" N X "ghi");
 }
 
+void FakeVimPlugin::test_vim_yank()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc" N "def");
+    COMMAND("y x", X "abc" N "def");
+    KEYS("\"xp", "abc" N X "abc" N "def");
+    COMMAND("u", X "abc" N "def");
+    COMMAND("redo", X "abc" N "abc" N "def");
+
+    KEYS("uw", "abc" N X "def");
+    COMMAND("1y y", "abc" N X "def");
+    KEYS("\"yP", "abc" N X "abc" N "def");
+    COMMAND("u", "abc" N X "def");
+
+    COMMAND("-1,$y x", "abc" N X "def");
+    KEYS("\"xP", "abc" N X "abc" N "def" N "def");
+    COMMAND("u", "abc" N X "def");
+
+    COMMAND("$-1y", "abc" N X "def");
+    KEYS("P", "abc" N X "abc" N "def");
+    COMMAND("u", "abc" N X "def");
+}
+
+void FakeVimPlugin::test_vim_move()
+{
+    TestData data;
+    setup(&data);
+
+    data.setText("abc" N "def" N "ghi" N "jkl");
+    COMMAND("m +1", "def" N X "abc" N "ghi" N "jkl");
+    COMMAND("u", X "abc" N "def" N "ghi" N "jkl");
+    COMMAND("redo", X "def" N "abc" N "ghi" N "jkl");
+    COMMAND("m -2", X "def" N "abc" N "ghi" N "jkl");
+    COMMAND("2m0", X "abc" N "def" N "ghi" N "jkl");
+
+    COMMAND("m $-2", "def" N X "abc" N "ghi" N "jkl");
+    KEYS("`'", X "def" N "abc" N "ghi" N "jkl");
+    KEYS("Vj:m+2<cr>", "ghi" N "def" N X "abc" N "jkl");
+    KEYS("u", X "def" N "abc" N "ghi" N "jkl");
+}
+
 void FakeVimPlugin::test_advanced_commands()
 {
     TestData data;
diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp
index c6e35978f2c..3ff27b25b2b 100644
--- a/src/plugins/fakevim/fakevimhandler.cpp
+++ b/src/plugins/fakevim/fakevimhandler.cpp
@@ -708,14 +708,14 @@ public:
 
     bool operator==(const Input &a) const
     {
-        return m_key == a.m_key && m_modifiers == a.m_modifiers && m_text == a.m_text;
+        return m_key == a.m_key && m_modifiers == a.m_modifiers;
     }
 
     bool operator!=(const Input &a) const { return !operator==(a); }
 
     bool operator<(const Input &a) const
     {
-        return m_key < a.m_key || m_modifiers < a.m_modifiers || m_text < a.m_text;
+        return m_key < a.m_key || m_modifiers < a.m_modifiers;
     }
 
     QString text() const { return m_text; }
@@ -1284,7 +1284,7 @@ public:
     // The following use all zero-based counting.
     int cursorLineOnScreen() const;
     int cursorLine() const;
-    int cursorBlockNumber() const;
+    int cursorBlockNumber() const; // "." address
     int physicalCursorColumn() const; // as stored in the data
     int logicalCursorColumn() const; // as visible on screen
     int physicalToLogicalColumn(int physical, const QString &text) const;
@@ -1583,6 +1583,8 @@ public:
     bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
     bool handleExBangCommand(const ExCommand &cmd);
     bool handleExDeleteCommand(const ExCommand &cmd);
+    bool handleExYankCommand(const ExCommand &cmd);
+    bool handleExMoveCommand(const ExCommand &cmd);
     bool handleExGotoCommand(const ExCommand &cmd);
     bool handleExHistoryCommand(const ExCommand &cmd);
     bool handleExRegisterCommand(const ExCommand &cmd);
@@ -4019,22 +4021,17 @@ int FakeVimHandler::Private::readLineCode(QString &cmd)
     //qDebug() << "CMD: " << cmd;
     if (cmd.isEmpty())
         return -1;
-    QChar c = cmd.at(0);
-    cmd = cmd.mid(1);
-    if (c == '.') {
-        if (cmd.isEmpty())
-            return cursorBlockNumber();
-        QChar c1 = cmd.at(0);
-        if (c1 == '+' || c1 == '-') {
-            // Repeat for things like  .+4
-            cmd = cmd.mid(1);
-            return cursorBlockNumber() + readLineCode(cmd);
-        }
-        return cursorBlockNumber();
-    }
-    if (c == '$')
-        return document()->blockCount() - 1;
-    if (c == '\'' && !cmd.isEmpty()) {
+
+    int result = -1;
+    const QChar &c = cmd[0];
+    if (c == '.') { // current line
+        result = cursorBlockNumber();
+        cmd.remove(0, 1);
+    } else if (c == '$') { // last line
+        result = document()->blockCount() - 1;
+        cmd.remove(0, 1);
+    } else if (c == '\'') { // mark
+        cmd.remove(0, 1);
         if (cmd.isEmpty()) {
             showMessage(MessageError, msgMarkNotSet(QString()));
             return -1;
@@ -4042,35 +4039,42 @@ int FakeVimHandler::Private::readLineCode(QString &cmd)
         CursorPosition m = mark(cmd.at(0).unicode());
         if (!m.isValid()) {
             showMessage(MessageError, msgMarkNotSet(cmd.at(0)));
-            cmd = cmd.mid(1);
             return -1;
         }
-        cmd = cmd.mid(1);
-        return m.line;
-    }
-    if (c == '-') {
-        int n = readLineCode(cmd);
-        return cursorBlockNumber() - (n == -1 ? 1 : n);
-    }
-    if (c == '+') {
-        int n = readLineCode(cmd);
-        return cursorBlockNumber() + (n == -1 ? 1 : n);
-    }
-    if (c.isDigit()) {
-        int n = c.unicode() - '0';
-        while (!cmd.isEmpty()) {
-            c = cmd.at(0);
-            if (!c.isDigit())
-                break;
-            cmd = cmd.mid(1);
-            n = n * 10 + (c.unicode() - '0');
+        cmd.remove(0, 1);
+        result = m.line;
+    } else if (c.isDigit()) { // line with given number
+        result = 0;
+    } else if (c == '-' || c == '+') { // add or subtract from current line number
+        result = cursorBlockNumber();
+    } else {
+        return -1;
+    }
+    // FIXME: /.../ ?...? \/ \? \&
+
+    // basic arithmetic ("-3+5" or "++" means "+2" etc.)
+    int n = 0;
+    bool add = true;
+    int i = 0;
+    for (; i < cmd.size(); ++i) {
+        const QChar &c = cmd[i];
+        if (c == '-' || c == '+') {
+            if (n != 0)
+                result = result + (add ? n - 1 : -(n - 1));
+            add = c == '+';
+            result = result + (add ? 1 : -1);
+            n = 0;
+        } else if (c.isDigit()) {
+            n = n * 10 + c.digitValue();
+        } else if (!c.isSpace()) {
+            break;
         }
-        //qDebug() << "N: " << n;
-        return n - 1;
     }
-    // Parsing failed.
-    cmd = c + cmd;
-    return -1;
+    if (n != 0)
+        result = result + (add ? n - 1 : -(n - 1));
+    cmd.remove(0, i);
+
+    return result;
 }
 
 void FakeVimHandler::Private::setCurrentRange(const Range &range)
@@ -4085,6 +4089,7 @@ Range FakeVimHandler::Private::rangeFromCurrentLine() const
     QTextBlock block = cursor().block();
     range.beginPos = block.position();
     range.endPos = range.beginPos + block.length() - 1;
+    range.rangemode = RangeLineMode;
     return range;
 }
 
@@ -4410,7 +4415,7 @@ bool FakeVimHandler::Private::handleExDeleteCommand(const ExCommand &cmd)
     if (!cmd.matches("d", "delete"))
         return false;
 
-    Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
+    Range range = cmd.range.endPos <= 0 ? rangeFromCurrentLine() : cmd.range;
     setCurrentRange(range);
     QString reg = cmd.args;
     QString text = selectText(range);
@@ -4422,6 +4427,69 @@ bool FakeVimHandler::Private::handleExDeleteCommand(const ExCommand &cmd)
     return true;
 }
 
+bool FakeVimHandler::Private::handleExYankCommand(const ExCommand &cmd)
+{
+    // :[range]y[ank] [x]
+    if (!cmd.matches("y", "yank"))
+        return false;
+
+    Range range = cmd.range.endPos <= 0 ? rangeFromCurrentLine() : cmd.range;
+    const int r = cmd.args.isEmpty() ? m_register : cmd.args.at(0).unicode();
+    yankText(range, r);
+
+    return true;
+}
+
+bool FakeVimHandler::Private::handleExMoveCommand(const ExCommand &cmd)
+{
+    // :[range]m[ove] {address}
+    QRegExp re("^m(?=ove)?([\\d+-$%./'\\\\?].*|$)"); // address can follow immediately
+    if (re.indexIn(cmd.cmd) == -1)
+        return false;
+
+    QString lineCode = re.cap(1) + cmd.args;
+
+    Range range = cmd.range.endPos <= 0 ? rangeFromCurrentLine() : cmd.range;
+    const int startLine = document()->findBlock(range.beginPos).blockNumber();
+    const int endLine = document()->findBlock(range.endPos).blockNumber();
+    const int lines = endLine - startLine + 1;
+
+    const int line = lineCode == "0" ? -1 : readLineCode(lineCode);
+    if (line >= startLine && line < endLine) {
+        showMessage(MessageError, FakeVimHandler::tr("Move lines into themselves"));
+        return true;
+    }
+
+    setUndoPosition();
+    recordJump();
+
+    setCurrentRange(range);
+    QString text = selectText(range);
+    removeText(currentRange());
+
+    bool insertAtEnd = line == document()->blockCount();
+    const int targetLine = (line < startLine) ? line : line - lines;
+    QTextBlock block = document()->findBlockByNumber(insertAtEnd ? targetLine : targetLine + 1);
+    int pos = block.position();
+    setAnchorAndPosition(pos, pos);
+    if (insertAtEnd) {
+        moveToEndOfLine();
+        insertText(QString('\n'));
+    }
+    insertText(text);
+    if (insertAtEnd)
+        cursor().deleteChar();
+
+    moveUp(1);
+    if (hasConfig(ConfigStartOfLine))
+        moveToFirstNonBlankOnLine();
+
+    if (lines > 2)
+        showMessage(MessageInfo, FakeVimHandler::tr("%1 lines moved").arg(lines));
+
+    return true;
+}
+
 bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd)
 {
     // :w, :x, :wq, ...
@@ -4703,7 +4771,7 @@ bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
     if (line.startsWith(QLatin1Char('%')))
         line.replace(0, 1, "1,$");
 
-    const int beginLine = readLineCode(line);
+    int beginLine = readLineCode(line);
     int endLine = -1;
     if (line.startsWith(',')) {
         line = line.mid(1);
@@ -4724,6 +4792,8 @@ bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
         || handleExHistoryCommand(cmd)
         || handleExRegisterCommand(cmd)
         || handleExDeleteCommand(cmd)
+        || handleExYankCommand(cmd)
+        || handleExMoveCommand(cmd)
         || handleExMapCommand(cmd)
         || handleExNohlsearchCommand(cmd)
         || handleExNormalCommand(cmd)
@@ -5236,7 +5306,7 @@ int FakeVimHandler::Private::cursorLine() const
 
 int FakeVimHandler::Private::cursorBlockNumber() const
 {
-    return cursor().block().blockNumber();
+    return document()->findBlock(qMin(anchor(), position())).blockNumber();
 }
 
 int FakeVimHandler::Private::physicalCursorColumn() const
@@ -5423,6 +5493,11 @@ QString FakeVimHandler::Private::selectText(const Range &range) const
 void FakeVimHandler::Private::yankText(const Range &range, int reg)
 {
     setRegister(reg, selectText(range), range.rangemode);
+
+    const int lines = document()->findBlock(range.endPos).blockNumber()
+        - document()->findBlock(range.beginPos).blockNumber() + 1;
+    if (lines > 2)
+        showMessage(MessageInfo, FakeVimHandler::tr("%1 lines yanked").arg(lines));
 }
 
 void FakeVimHandler::Private::transformText(const Range &range,
@@ -6381,13 +6456,17 @@ void FakeVimHandler::Private::setRegister(int reg, const QString &contents, Rang
     bool copyToSelection;
     getRegisterType(reg, &copyToClipboard, &copyToSelection);
 
+    QString contents2 = contents;
+    if (mode == RangeLineMode && !contents2.endsWith('\n'))
+        contents2.append('\n');
+
     if (copyToClipboard || copyToSelection) {
         if (copyToClipboard)
-            setClipboardData(contents, mode, QClipboard::Clipboard);
+            setClipboardData(contents2, mode, QClipboard::Clipboard);
         if (copyToSelection)
-            setClipboardData(contents, mode, QClipboard::Selection);
+            setClipboardData(contents2, mode, QClipboard::Selection);
     } else {
-        g.registers[reg].contents = contents;
+        g.registers[reg].contents = contents2;
         g.registers[reg].rangemode = mode;
     }
 }
diff --git a/src/plugins/fakevim/fakevimplugin.h b/src/plugins/fakevim/fakevimplugin.h
index a664c352be5..4525fe4faec 100644
--- a/src/plugins/fakevim/fakevimplugin.h
+++ b/src/plugins/fakevim/fakevimplugin.h
@@ -81,6 +81,8 @@ private slots:
     void test_vim_code_autoindent();
     void test_vim_code_folding();
     void test_vim_substitute();
+    void test_vim_yank();
+    void test_vim_move();
     void test_advanced_commands();
     void test_map();
 
-- 
GitLab