diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp index fa64c00bf9c..2205262aa7e 100644 --- a/src/plugins/fakevim/fakevim_test.cpp +++ b/src/plugins/fakevim/fakevim_test.cpp @@ -699,3 +699,120 @@ void FakeVimPlugin::test_vim_undo_redo() KEYS("jlllSxyz", "abc" N "xyz" N "ghi"); KEYS("u", "abc" N " " X "def" N "ghi"); } + +void FakeVimPlugin::test_map() +{ + TestData data; + setup(&data); + + data.setText("abc def"); + data.doCommand("map C ix"); + data.doCommand("map c iXXX"); + data.doCommand("imap c YYY"); + KEYS("C", " x" X " abc def"); + data.doCommand("map C "); + KEYS("C", " x" X " abc def"); + data.doCommand("map C i"); + KEYS("C", " " X " abc def"); + KEYS("ccc", " XXXYYY YYY" X " abc def"); + // unmap + KEYS(":unmap cccc", "YYY" X " "); + KEYS(":iunmap cccc", X "c"); + data.doCommand("unmap C"); + + data.setText("abc def"); + data.doCommand("imap x ((()))"); + KEYS("x", X "bc def"); + KEYS("ix", "((( bc))" X ") def"); + data.doCommand("iunmap x"); + + data.setText("abc def"); + data.doCommand("map 3l"); + KEYS("", "abc" X " def"); + KEYS("", "abc de" X "f"); + + // map vs. noremap + data.setText("abc def"); + data.doCommand("map x 3l"); + data.doCommand("map X x"); + KEYS("X", "abc" X " def"); + data.doCommand("noremap X x"); + KEYS("X", "abc" X "def"); + data.doCommand("unmap X"); + data.doCommand("unmap x"); + + // limit number of recursions in mappings + data.doCommand("map X Y"); + data.doCommand("map Y Z"); + data.doCommand("map Z X"); + KEYS("X", "abc" X "def"); + data.doCommand("map Z i"); + KEYS("X", "abc" X " def"); + data.doCommand("unmap X"); + data.doCommand("unmap Y"); + data.doCommand("unmap Z"); + + // imcomplete mapping + data.setText("abc"); + data.doCommand("map Xa ia"); + data.doCommand("map Xb ib"); + data.doCommand("map X ic"); + KEYS("Xa", X "aabc"); + KEYS("Xb", X "baabc"); + KEYS("Xic", X "ccbaabc"); + + // unmap + data.doCommand("unmap Xa"); + KEYS("Xa", X "cccbaabc"); + data.doCommand("unmap Xb"); + KEYS("Xb", X "ccccbaabc"); + data.doCommand("unmap X"); + KEYS("Xb", X "ccccbaabc"); + KEYS("X", X "ccccbaabc"); + + // recursive mapping + data.setText("abc"); + data.doCommand("map X Y"); + data.doCommand("map XXX i1"); + data.doCommand("map Y i2"); + data.doCommand("map YZ i3"); + data.doCommand("map _ i "); + KEYS("_XXX_", X " 1 abc"); + KEYS("XX_0", X " 22 1 abc"); + KEYS("XXXXZ_0", X " 31 22 1 abc"); + KEYS("XXXXX_0", X " 221 31 22 1 abc"); + KEYS("XXZ", X "32 221 31 22 1 abc"); + data.doCommand("unmap X"); + data.doCommand("unmap XXX"); + data.doCommand("unmap Y"); + data.doCommand("unmap YZ"); + data.doCommand("unmap _"); + + // shift modifier + data.setText("abc"); + data.doCommand("map x i1"); + data.doCommand("map X i2"); + KEYS("x", X "1abc"); + KEYS("X", X "21abc"); + data.doCommand("map i3"); + KEYS("X", X "321abc"); + data.doCommand("map X i4"); + KEYS("X", X "4321abc"); + KEYS("x", X "14321abc"); + data.doCommand("unmap x"); + data.doCommand("unmap X"); + + // undo/redo mapped input + data.setText("abc def ghi"); + data.doCommand("map X dwea xyz3l"); + KEYS("X", "def xyz g" X "hi"); + KEYS("u", X "abc def ghi"); + KEYS("", X "def xyz ghi"); + data.doCommand("unmap X"); + + NOT_IMPLEMENTED + // + data.setText("abc def"); + data.doCommand("imap X :%s/def/xxx/"); + KEYS("iX", "abc xxx"); +} diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index d7feb46419f..9d9d58ff38b 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -307,9 +307,18 @@ struct SearchData bool highlightCursor; }; +// If string begins with given prefix remove it with trailing spaces and return true. +static bool eatString(const QString &prefix, QString *str) +{ + if (!str->startsWith(prefix)) + return false; + *str = str->mid(prefix.size()).trimmed(); + return true; +} + static QRegExp vimPatternToQtPattern(QString needle, bool smartcase) { - /* Trasformations (Vim regexp -> QRegExp): + /* Transformations (Vim regexp -> QRegExp): * \a -> [A-Za-z] * \A -> [^A-Za-z] * \h -> [A-Za-z_] @@ -541,7 +550,11 @@ public: : m_key(0), m_xkey(0), m_modifiers(0) {} explicit Input(QChar x) - : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {} + : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) + { + if (x.isUpper()) + m_modifiers = Qt::ShiftModifier; + } Input(int k, int m, const QString &t) : m_key(k), m_modifiers(cleanModifier(m)), m_text(t) @@ -556,6 +569,11 @@ public: m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key); } + bool isValid() const + { + return m_key != 0 || !m_text.isNull(); + } + bool isDigit() const { return m_xkey >= '0' && m_xkey <= '9'; @@ -600,18 +618,17 @@ public: bool operator==(const Input &a) const { - return a.m_key == m_key && a.m_modifiers == m_modifiers + return a.m_xkey == m_xkey && a.m_modifiers == m_modifiers && m_text == a.m_text; } - // Ignore e.g. ShiftModifier, which is not available in sourced data. - bool matchesForMap(const Input &a) const - { - return (a.m_key == m_key || a.m_xkey == m_xkey) && m_text == a.m_text; - } - 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; + } + QString text() const { return m_text; } QChar asChar() const @@ -642,54 +659,177 @@ private: QString m_text; }; +// mapping to (do nothing) +static const Input Nop(-1, -1, QString()); + QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); } class Inputs : public QVector { public: - Inputs() {} - explicit Inputs(const QString &str) { parseFrom(str); } + Inputs() : m_noremap(true), m_silent(false) {} + + explicit Inputs(const QString &str, bool noremap = true, bool silent = false) + : m_noremap(noremap), m_silent(silent) + { + parseFrom(str); + } + + bool noremap() const { return m_noremap; } + + bool silent() const { return m_silent; } + +private: void parseFrom(const QString &str); + + bool m_noremap; + bool m_silent; }; -static bool iss(char a, char b) +static QMap vimKeyNames() { - if (a >= 'a') - a -= 'a' - 'A'; - if (b >= 'a') - b -= 'a' - 'A'; - return a == b; + QMap k; + + // FIXME: Should be value of mapleader. + k.insert("LEADER", Key_Backslash); + + k.insert("SPACE", Key_Space); + k.insert("TAB", Key_Tab); + k.insert("NL", Key_Return); + k.insert("NEWLINE", Key_Return); + k.insert("LINEFEED", Key_Return); + k.insert("LF", Key_Return); + k.insert("CR", Key_Return); + k.insert("RETURN", Key_Return); + k.insert("ENTER", Key_Return); + k.insert("BS", Key_Backspace); + k.insert("BACKSPACE", Key_Backspace); + k.insert("ESC", Key_Escape); + k.insert("BAR", Key_Bar); + k.insert("BSLASH", Key_Backslash); + k.insert("DEL", Key_Delete); + k.insert("DELETE", Key_Delete); + k.insert("KDEL", Key_Delete); + k.insert("UP", Key_Up); + k.insert("DOWN", Key_Down); + k.insert("LEFT", Key_Left); + k.insert("RIGHT", Key_Right); + + k.insert("F1", Key_F1); + k.insert("F2", Key_F2); + k.insert("F3", Key_F3); + k.insert("F4", Key_F4); + k.insert("F5", Key_F5); + k.insert("F6", Key_F6); + k.insert("F7", Key_F7); + k.insert("F8", Key_F8); + k.insert("F9", Key_F9); + k.insert("F10", Key_F10); + + k.insert("F11", Key_F11); + k.insert("F12", Key_F12); + k.insert("F13", Key_F13); + k.insert("F14", Key_F14); + k.insert("F15", Key_F15); + k.insert("F16", Key_F16); + k.insert("F17", Key_F17); + k.insert("F18", Key_F18); + k.insert("F19", Key_F19); + k.insert("F20", Key_F20); + + k.insert("F21", Key_F21); + k.insert("F22", Key_F22); + k.insert("F23", Key_F23); + k.insert("F24", Key_F24); + k.insert("F25", Key_F25); + k.insert("F26", Key_F26); + k.insert("F27", Key_F27); + k.insert("F28", Key_F28); + k.insert("F29", Key_F29); + k.insert("F30", Key_F30); + + k.insert("F31", Key_F31); + k.insert("F32", Key_F32); + k.insert("F33", Key_F33); + k.insert("F34", Key_F34); + k.insert("F35", Key_F35); + + k.insert("INSERT", Key_Insert); + k.insert("INS", Key_Insert); + k.insert("KINSERT", Key_Insert); + k.insert("HOME", Key_Home); + k.insert("END", Key_End); + k.insert("PAGEUP", Key_PageUp); + k.insert("PAGEDOWN", Key_PageDown); + + k.insert("KPLUS", Key_Plus); + k.insert("KMINUS", Key_Minus); + k.insert("KDIVIDE", Key_Slash); + k.insert("KMULTIPLY", Key_Asterisk); + k.insert("KENTER", Key_Enter); + k.insert("KPOINT", Key_Period); + + return k; +} + +static Input parseVimKeyName(const QString &keyName) +{ + if (keyName.length() == 1) + return Input(keyName.at(0)); + + const QStringList keys = keyName.split('-'); + const int len = keys.length(); + + if (len == 1 && keys.at(0) == _("nop")) + return Nop; + + int mods = NoModifier; + for (int i = 0; i < len - 1; ++i) { + const QString &key = keys[i].toUpper(); + if (key == "S") + mods |= Qt::ShiftModifier; + else if (key == "C") + mods |= RealControlModifier; + else + return Input(); + } + + if (!keys.isEmpty()) { + const QString key = keys.last(); + if (key.length() == 1) { + // simple character + QChar c = key.at(0).toUpper(); + return Input(c.unicode(), mods, QString(c)); + } + + // find key name + static const QMap k = vimKeyNames(); + QMap::ConstIterator it = k.constFind(key.toUpper()); + if (it != k.end()) + return Input(*it, mods, *it <= 0x7f ? QString(QChar::fromAscii(*it)) : QString("")); + } + + return Input(); } void Inputs::parseFrom(const QString &str) { const int n = str.size(); for (int i = 0; i < n; ++i) { - uint c0 = str.at(i).unicode(), c1 = 0, c2 = 0, c3 = 0, c4 = 0; - if (i + 1 < n) - c1 = str.at(i + 1).unicode(); - if (i + 2 < n) - c2 = str.at(i + 2).unicode(); - if (i + 3 < n) - c3 = str.at(i + 3).unicode(); - if (i + 4 < n) - c4 = str.at(i + 4).unicode(); - if (c0 == '<') { - if (iss(c1, 'C') && c2 == '-' && c4 == '>') { - uint c = (c3 < 90 ? c3 : c3 - 32); - append(Input(c, RealControlModifier, QString(QChar(c - 64)))); - i += 4; - } else if (iss(c1, 'C') && iss(c2, 'R') && c3 == '>') { - append(Input(Key_Return, Qt::NoModifier, QString(QChar(13)))); - i += 3; - } else if (iss(c1, 'E') && iss(c2, 'S') && iss(c3, 'C') && c4 == '>') { - append(Input(Key_Escape, Qt::NoModifier, QString(QChar(27)))); - i += 4; + uint c = str.at(i).unicode(); + if (c == '<') { + int j = str.indexOf('>', i); + Input input; + if (j != -1) + input = parseVimKeyName(str.mid(i+1, j - i - 1)); + if (input.isValid()) { + append(input); + i = j; } else { - append(Input(QLatin1Char(c0))); + append(Input(QLatin1Char(c))); } } else { - append(Input(QLatin1Char(c0))); + append(Input(QLatin1Char(c))); } } } @@ -742,7 +882,7 @@ const QString &History::move(const QStringRef &prefix, int skip) class CommandBuffer { public: - CommandBuffer() : m_pos(0), m_userPos(0) {} + CommandBuffer() : m_pos(0), m_userPos(0), m_historyAutoSave(true) {} void setPrompt(const QChar &prompt) { m_prompt = prompt; } void setContents(const QString &s) { m_buffer = s; m_pos = s.size(); } @@ -765,6 +905,7 @@ public: void moveStart() { m_userPos = m_pos = 0; } void moveEnd() { m_userPos = m_pos = m_buffer.size(); } + void setHistoryAutoSave(bool autoSave) { m_historyAutoSave = autoSave; } void historyDown() { setContents(m_history.move(userContents(), 1)); } void historyUp() { setContents(m_history.move(userContents(), -1)); } const QStringList &historyItems() const { return m_history.items(); } @@ -774,7 +915,13 @@ public: m_history.append(item.isNull() ? contents() : item); } - void clear() { historyPush(); m_buffer.clear(); m_userPos = m_pos = 0; } + void clear() + { + if (m_historyAutoSave) + historyPush(); + m_buffer.clear(); + m_userPos = m_pos = 0; + } QString display() const { @@ -820,74 +967,151 @@ private: History m_history; int m_pos; int m_userPos; // last position of inserted text (for retrieving history items) + bool m_historyAutoSave; // store items to history on clear()? }; -// Mappings for a specific mode. -class ModeMapping : public QList > +// Mappings for a specific mode (trie structure) +class ModeMapping : public QMap { public: - ModeMapping() { test(); } + const Inputs &value() const { return m_value; } + void setValue(const Inputs &value) { m_value = value; } +private: + Inputs m_value; +}; - void test() +// Mappings for all modes +typedef QHash Mappings; + +// Iterator for mappings +class MappingsIterator : public QVector +{ +public: + MappingsIterator(Mappings *mappings, char mode = -1, const Inputs &inputs = Inputs()) + : m_parent(mappings) { - //insert(Inputs() << Input('A') << Input('A'), - // Inputs() << Input('x') << Input('x')); + reset(mode); + walk(inputs); } - void insert(const Inputs &from, const Inputs &to) + // Reset iterator state. Keep previous mode if 0. + void reset(char mode = 0) { - for (int i = 0; i != size(); ++i) - if (at(i).first == from) { - (*this)[i].second = to; - return; - } - append(QPair(from, to)); - } - - void remove(const Inputs &from) - { - for (int i = 0; i != size(); ++i) - if (at(i).first == from) { - removeAt(i); - return; - } - } - - // Returns 'false' if more input is needed to decide whether a mapping - // needs to be applied. If a decision can be made, return 'true', - // and replace *input with the mapped data. - bool mappingDone(Inputs *inputs) const - { - // FIXME: inefficient. - for (int i = 0; i != size(); ++i) { - const Inputs &haystack = at(i).first; - // A mapping - if (couldTriggerMap(haystack, *inputs)) { - if (haystack.size() != inputs->size()) - return false; // This can be extended. - // Actual mapping. - *inputs = at(i).second; - return true; - } + clear(); + m_lastValid = -1; + m_invalidInputCount = 0; + if (mode != 0) { + m_mode = mode; + if (mode != -1) + m_modeMapping = m_parent->find(mode); } - // No extensible mapping found. Use inputs as-is. + } + + bool isValid() const { return !empty(); } + + // Return true if mapping can be extended. + bool canExtend() const { return isValid() && !last()->empty(); } + + // Return true if this mapping can be used. + bool isComplete() const { return m_lastValid != -1; } + + // Return size of current map. + int mapLength() const { return m_lastValid + 1; } + + int invalidInputCount() const { return m_invalidInputCount; } + + bool walk(const Input &input) + { + if (m_modeMapping == m_parent->end()) + return false; + + if (!input.isValid()) { + m_invalidInputCount += 1; + return true; + } + + ModeMapping::Iterator it; + if (isValid()) { + it = last()->find(input); + if (it == last()->end()) + return false; + } else { + it = m_modeMapping->find(input); + if (it == m_modeMapping->end()) + return false; + } + + if (!it->value().isEmpty()) + m_lastValid = size(); + append(it); + return true; } -private: - static bool couldTriggerMap(const Inputs &haystack, const Inputs &needle) + bool walk(const Inputs &inputs) { - // Input is already too long. - if (needle.size() > haystack.size()) - return false; - for (int i = 0; i != needle.size(); ++i) { - if (!needle.at(i).matchesForMap(haystack.at(i))) + foreach (const Input &input, inputs) { + if (!walk(input)) return false; } return true; } + + // Return current mapped value. Iterator must be valid. + const Inputs &inputs() const + { + return at(m_lastValid)->value(); + } + + void remove() + { + if (isValid()) { + if (canExtend()) { + last()->setValue(Inputs()); + } else { + if (size() > 1) { + while (last()->empty()) { + at(size() - 2)->erase(last()); + pop_back(); + if (size() == 1 || !last()->value().isEmpty()) + break; + } + if (last()->empty() && last()->value().isEmpty()) + m_modeMapping->erase(last()); + } else if (last()->empty() && !last()->value().isEmpty()) { + m_modeMapping->erase(last()); + } + } + } + } + + void setInputs(const Inputs &key, const Inputs &inputs, bool unique = false) + { + ModeMapping *current = &(*m_parent)[m_mode]; + foreach (const Input &input, key) + current = &(*current)[input]; + if (!unique || current->value().isEmpty()) + current->setValue(inputs); + } + +private: + Mappings *m_parent; + Mappings::Iterator m_modeMapping; + int m_lastValid; + int m_invalidInputCount; + char m_mode; }; +// state of current mapping +struct MappingState { + MappingState() + : maxMapDepth(1000), noremap(false), silent(false) {} + MappingState(int depth, bool noremap, bool silent) + : maxMapDepth(depth), noremap(noremap), silent(silent) {} + int maxMapDepth; + bool noremap; + bool silent; +}; class FakeVimHandler::Private : public QObject { @@ -909,8 +1133,9 @@ public: friend class FakeVimHandler; void init(); - EventResult handleKey(const Input &); - Q_SLOT EventResult handleKey2(); + EventResult handleKey(const Input &input); + EventResult handleDefaultKey(const Input &input); + Q_SLOT void handleMappedKeys(); EventResult handleInsertMode(const Input &); EventResult handleReplaceMode(const Input &); EventResult handleCommandMode(const Input &); @@ -1100,6 +1325,7 @@ public: bool isVisualCharMode() const { return m_visualMode == VisualCharMode; } bool isVisualLineMode() const { return m_visualMode == VisualLineMode; } bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; } + char currentModeCode() const; void updateEditor(); void selectTextObject(bool simple, bool inner); @@ -1300,23 +1526,26 @@ public: static struct GlobalData { - GlobalData() + GlobalData() : mappings(), currentMap(&mappings), inputTimer(-1) { - inputTimer = -1; + // default mapping state - shouldn't be removed + mapStates << MappingState(); } - // Input. - Inputs pendingInput; - int inputTimer; - // Repetition. QString dotCommand; QHash registers; // All mappings. - typedef QHash Mappings; Mappings mappings; + + // Input. + Inputs pendingInput; + MappingsIterator currentMap; + int inputTimer; + int lastMapCode; + QStack mapStates; } g; }; @@ -1639,61 +1868,114 @@ void FakeVimHandler::Private::restoreWidget(int tabSize) EventResult FakeVimHandler::Private::handleKey(const Input &input) { KEY_DEBUG("HANDLE INPUT: " << input << " MODE: " << mode); - if (m_mode == ExMode) - return handleExMode(input); - if (m_subsubmode == SearchSubSubMode) - return handleSearchSubSubMode(input); - if (m_mode == InsertMode || m_mode == ReplaceMode || m_mode == CommandMode) { - g.pendingInput.append(input); - const char code = m_mode == InsertMode ? 'i' : 'n'; - if (g.mappings.value(code).mappingDone(&g.pendingInput)) - return handleKey2(); - if (g.inputTimer != -1) - killTimer(g.inputTimer); - g.inputTimer = startTimer(1000); - return EventHandled; + + if (g.inputTimer != -1) { + killTimer(g.inputTimer); + g.inputTimer = -1; } + + EventResult r = EventUnhandled; + if (input.isValid()) { + g.pendingInput.append(input); + if (g.currentMap.isValid()) { + if (!g.currentMap.walk(input) && g.currentMap.isComplete()) + handleMappedKeys(); + } + } + + while (!g.pendingInput.isEmpty()) { + const Input &in = g.pendingInput.front(); + + // invalid input is used to pop mapping state + if (!in.isValid()) { + g.mapStates.pop_back(); + QTC_CHECK(!g.mapStates.empty()); + endEditBlock(); + if (g.mapStates.size() == 1) + m_commandBuffer.setHistoryAutoSave(true); + if (m_mode == ExMode || m_subsubmode == SearchSubSubMode) + updateMiniBuffer(); // update cursor position on command line + } else { + if (!g.mapStates.last().noremap && m_subsubmode != SearchSubSubMode) { + if (!g.currentMap.isValid()) { + g.currentMap.reset(currentModeCode()); + if (!g.currentMap.walk(g.pendingInput) && g.currentMap.isComplete()) { + handleMappedKeys(); + continue; + } + } + + // handle user mapping + if (g.currentMap.canExtend()) { + // wait for user to press any key or trigger mapping after interval + g.inputTimer = startTimer(1000); + return EventHandled; + } else if (g.currentMap.isComplete()) { + handleMappedKeys(); + continue; + } + } + + r = handleDefaultKey(in); + // TODO: Unhadled events! + } + g.pendingInput.pop_front(); + } + + return r; +} + +EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input) +{ + if (input == Nop) + return EventHandled; + else if (m_subsubmode == SearchSubSubMode) + return handleSearchSubSubMode(input); + else if (m_mode == CommandMode) + return handleCommandMode(input); + else if (m_mode == InsertMode) + return handleInsertMode(input); + else if (m_mode == ReplaceMode) + return handleReplaceMode(input); + else if (m_mode == ExMode) + return handleExMode(input); return EventUnhandled; } -EventResult FakeVimHandler::Private::handleKey2() +void FakeVimHandler::Private::handleMappedKeys() { - Inputs pendingInput = g.pendingInput; - g.pendingInput.clear(); - if (m_mode == InsertMode) { - EventResult result = EventUnhandled; - foreach (const Input &in, pendingInput) { - EventResult r = handleInsertMode(in); - if (r == EventHandled) - result = EventHandled; - } - return result; + int maxMapDepth = g.mapStates.last().maxMapDepth - 1; + + int invalidCount = g.currentMap.invalidInputCount(); + if (invalidCount > 0) { + g.mapStates.remove(g.mapStates.size() - invalidCount, invalidCount); + QTC_CHECK(!g.mapStates.empty()); + for (int i = 0; i < invalidCount; ++i) + endEditBlock(); } - if (m_mode == ReplaceMode) { - EventResult result = EventUnhandled; - foreach (const Input &in, pendingInput) { - EventResult r = handleReplaceMode(in); - if (r == EventHandled) - result = EventHandled; - } - return result; + + if (maxMapDepth <= 0) { + showRedMessage("recursive mapping"); + g.pendingInput.remove(0, g.currentMap.mapLength() + invalidCount); + } else { + const Inputs &inputs = g.currentMap.inputs(); + QVector rest = g.pendingInput.mid(g.currentMap.mapLength() + invalidCount); + g.pendingInput.clear(); + g.pendingInput << inputs << Input() << rest; + g.mapStates << MappingState(maxMapDepth, inputs.noremap(), inputs.silent()); + m_commandBuffer.setHistoryAutoSave(false); + beginEditBlock(); } - if (m_mode == CommandMode) { - EventResult result = EventUnhandled; - foreach (const Input &in, pendingInput) { - EventResult r = handleCommandMode(in); - if (r == EventHandled) - result = EventHandled; - } - return result; - } - return EventUnhandled; + g.currentMap.reset(); } void FakeVimHandler::Private::timerEvent(QTimerEvent *ev) { - Q_UNUSED(ev); - handleKey2(); + if (ev->timerId() == g.inputTimer) { + if (g.currentMap.isComplete()) + handleMappedKeys(); + handleKey(Input()); + } } void FakeVimHandler::Private::stopIncrementalFind() @@ -2039,12 +2321,22 @@ void FakeVimHandler::Private::updateMiniBuffer() QString msg; int cursorPos = -1; - bool interactive = (m_mode == ExMode || m_subsubmode == SearchSubSubMode); + if (m_passing) { msg = "-- PASSING -- "; - } else if (!m_currentMessage.isEmpty() && !interactive) { + } else if (m_subsubmode == SearchSubSubMode) { + msg = m_searchBuffer.display(); + if (g.mapStates.size() == 1) + cursorPos = m_searchBuffer.cursorPos() + 1; + } else if (m_mode == ExMode) { + msg = m_commandBuffer.display(); + if (g.mapStates.size() == 1) + cursorPos = m_commandBuffer.cursorPos() + 1; + } else if (!m_currentMessage.isEmpty()) { msg = m_currentMessage; - m_currentMessage.clear(); + } else if (g.mapStates.size() > 1 && !g.mapStates.last().silent) { + // Do not reset previous message when after running a mapped command. + return; } else if (m_mode == CommandMode && isVisualMode()) { if (isVisualCharMode()) { msg = "-- VISUAL --"; @@ -2057,17 +2349,13 @@ void FakeVimHandler::Private::updateMiniBuffer() msg = "-- INSERT --"; } else if (m_mode == ReplaceMode) { msg = "-- REPLACE --"; - } else if (m_subsubmode == SearchSubSubMode) { - msg = m_searchBuffer.display(); - cursorPos = m_searchBuffer.cursorPos() + 1; - } else if (m_mode == ExMode) { - msg = m_commandBuffer.display(); - cursorPos = m_commandBuffer.cursorPos() + 1; } else { QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode); msg = "-- COMMAND --"; } + m_currentMessage.clear(); + emit q->commandBufferChanged(msg, cursorPos); int linesInDoc = linesInDocument(); @@ -3492,8 +3780,6 @@ EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input) highlightMatches(needle); m_searchBuffer.clear(); } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) { - // FIXME: This and the three cases below are wrong as vim - // takes only matching entries in the history into account. m_searchBuffer.historyUp(); } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) { m_searchBuffer.historyDown(); @@ -3779,31 +4065,50 @@ bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map else return false; - const int pos = cmd0.args.indexOf(QLatin1Char(' ')); - if (pos == -1) { + QString args = cmd0.args; + bool silent = false; + bool unique = false; + forever { + if (eatString("", &args)) { + silent = true; + } else if (eatString("", &args)) { + continue; + } else if (eatString("", &args)) { + continue; + } else if (eatString("", &args)) { + notImplementedYet(); + continue; + } else if (eatString("