diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp index f19b5b1d58d152fe62ae658833872a0e32ece5e6..a17fbe146c15f6f32c855d335634a3b359a6eb20 100644 --- a/src/plugins/fakevim/fakevimactions.cpp +++ b/src/plugins/fakevim/fakevimactions.cpp @@ -209,10 +209,13 @@ FakeVimSettings *theFakeVimSettings() item->setSettingsKey(group, _("IsKeyword")); instance->insertItem(ConfigIsKeyword, item, _("iskeyword"), _("isk")); + // Invented here. item = new SavedAction(instance); - item->setText(QCoreApplication::translate("FakeVim::Internal", - "FakeVim properties...")); - instance->insertItem(SettingsDialog, item); + item->setDefaultValue(false); + item->setValue(false); + item->setSettingsKey(group, _("ShowMarks")); + item->setCheckable(true); + instance->insertItem(ConfigShowMarks, item, _("showmarks"), _("sm")); return instance; } diff --git a/src/plugins/fakevim/fakevimactions.h b/src/plugins/fakevim/fakevimactions.h index a05e129c9be5e3f3a7c5fa874a5d9bfc315f61b9..5929376bc68f95a26d53c37d3c86c77c5811dad6 100644 --- a/src/plugins/fakevim/fakevimactions.h +++ b/src/plugins/fakevim/fakevimactions.h @@ -64,7 +64,7 @@ enum FakeVimSettingsCode ConfigIsKeyword, // other actions - SettingsDialog, + ConfigShowMarks, }; class FakeVimSettings : public QObject diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index a2155143f44ec5df33c082bcc40d69e1d0de25e1..799e83f42aa63a0505af2a37abab87ebdf0e7bcb 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -56,6 +56,7 @@ // m_tc.position() (== position()). The character below position() is not included // if the last movement command was exclusive (MoveExclusive). // The value of m_tc.anchor() is not used. +// #include "fakevimhandler.h" #include "fakevimsyntax.h" @@ -69,6 +70,7 @@ #include <QtCore/QProcess> #include <QtCore/QRegExp> #include <QtCore/QTextStream> +#include <QtCore/QTimer> #include <QtCore/QtAlgorithms> #include <QtCore/QStack> @@ -126,51 +128,57 @@ namespace Internal { #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s) const int ParagraphSeparator = 0x00002029; +typedef QLatin1String _; using namespace Qt; +/*! A \e Mode represents one of the basic modes of operation of FakeVim. +*/ enum Mode { InsertMode, + ReplaceMode, CommandMode, ExMode, - SearchForwardMode, - SearchBackwardMode, }; +/*! A \e SubMode is used for things that require one more data item + and are 'nested' behind a \l Mode. +*/ enum SubMode { NoSubMode, - ChangeSubMode, // used for c - DeleteSubMode, // used for d - FilterSubMode, // used for ! - IndentSubMode, // used for = - RegisterSubMode, // used for " - ReplaceSubMode, // used for R and r - ShiftLeftSubMode, // used for < - ShiftRightSubMode, // used for > - TransformSubMode, // used for ~/gu/gU - WindowSubMode, // used for Ctrl-w - YankSubMode, // used for y - ZSubMode, // used for z - CapitalZSubMode // used for Z + ChangeSubMode, // Used for c + DeleteSubMode, // Used for d + FilterSubMode, // Used for ! + IndentSubMode, // Used for = + RegisterSubMode, // Used for " + ShiftLeftSubMode, // Used for < + ShiftRightSubMode, // Used for > + TransformSubMode, // Used for ~/gu/gU + WindowSubMode, // Used for Ctrl-w + YankSubMode, // Used for y + ZSubMode, // Used for z + CapitalZSubMode, // Used for Z + ReplaceSubMode, // Used for r }; +/*! A \e SubSubMode is used for things that require one more data item + and are 'nested' behind a \l SubMode. +*/ enum SubSubMode { - // typically used for things that require one more data item - // and are 'nested' behind a mode NoSubSubMode, - FtSubSubMode, // used for f, F, t, T - MarkSubSubMode, // used for m - BackTickSubSubMode, // used for ` - TickSubSubMode, // used for ' - InvertCaseSubSubMode, // used for ~ - DownCaseSubSubMode, // used for gu - UpCaseSubSubMode, // used for gU - ReplaceSubSubMode, // used for r after visual mode - TextObjectSubSubMode, // used for thing like iw, aW, as etc. + FtSubSubMode, // Used for f, F, t, T. + MarkSubSubMode, // Used for m. + BackTickSubSubMode, // Used for `. + TickSubSubMode, // Used for '. + InvertCaseSubSubMode, // Used for ~. + DownCaseSubSubMode, // Used for gu. + UpCaseSubSubMode, // Used for gU. + TextObjectSubSubMode, // Used for thing like iw, aW, as etc. + SearchSubSubMode, }; enum VisualMode @@ -188,14 +196,27 @@ enum MoveType MoveLineWise, }; -enum RangeMode -{ - RangeCharMode, // v - RangeLineMode, // V - RangeLineModeExclusive, // like above, but keep one newline when deleting - RangeBlockMode, // Ctrl-v - RangeBlockAndTailMode, // Ctrl-v for D and X -}; +/*! + \enum RangeMode + + The \e RangeMode serves as a means to define how the "Range" between + the \l cursor and the \l anchor position is to be interpreted. + + \value RangeCharMode Entered by pressing \key v. The range includes + all characters between cursor and anchor. + \value RangeLineMode Entered by pressing \key V. The range includes + all lines between the line of the cursor and + the line of the anchor. + \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one + newline when deleting. + \value RangeBlockMode Entered by pressing \key Ctrl-v. The range includes + all characters with line and column coordinates + between line and columns coordinates of cursor and + anchor. + \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes + all characters in the affected lines up to the end + of these lines. +*/ enum EventResult { @@ -207,8 +228,8 @@ enum EventResult struct Column { Column(int p, int l) : physical(p), logical(l) {} - int physical; // number of characters in the data - int logical; // column on screen + int physical; // Number of characters in the data. + int logical; // Column on screen. }; struct CursorPosition @@ -223,33 +244,63 @@ struct CursorPosition struct Register { Register() : rangemode(RangeCharMode) {} + Register(const QString &c) : contents(c), rangemode(RangeCharMode) {} Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {} QString contents; RangeMode rangemode; }; -struct Range +QDebug operator<<(QDebug ts, const Register ®) { - Range() - : beginPos(-1), endPos(-1), rangemode(RangeCharMode) - {} + return ts << reg.contents; +} - Range(int b, int e, RangeMode m = RangeCharMode) - : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m) - {} +struct SearchData +{ + SearchData() { init(); } - QString toString() const - { - return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos) - .arg(rangemode); - } + void init() { forward = true; mustMove = true; highlightMatches = true; + highlightCursor = true; } - int beginPos; - int endPos; - RangeMode rangemode; + QString needle; + bool forward; + bool mustMove; + bool highlightMatches; + bool highlightCursor; }; -QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels) + +Range::Range() + : beginPos(-1), endPos(-1), rangemode(RangeCharMode) +{} + +Range::Range(int b, int e, RangeMode m) + : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m) +{} + +QString Range::toString() const +{ + return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos) + .arg(rangemode); +} + +QDebug operator<<(QDebug ts, const Range &range) +{ + return ts << '[' << range.beginPos << ',' << range.endPos << ']'; +} + + + +ExCommand::ExCommand(const QString &c, const QString &a, const Range &r) + : cmd(c), hasBang(false), args(a), range(r) +{} + +QDebug operator<<(QDebug ts, const ExCommand &cmd) +{ + return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range; +} + +QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels) { foreach (const QTextEdit::ExtraSelection &sel, sels) ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position(); @@ -260,11 +311,14 @@ QString quoteUnprintable(const QString &ba) { QString res; for (int i = 0, n = ba.size(); i != n; ++i) { - QChar c = ba.at(i); + const QChar c = ba.at(i); + const int cc = c.unicode(); if (c.isPrint()) res += c; + else if (cc == '\n') + res += _("<CR>"); else - res += QString("\\x%1").arg(c.unicode(), 2, 16); + res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0')); } return res; } @@ -280,9 +334,9 @@ static bool startsWithWhitespace(const QString &str, int col) return true; } -inline QString msgE20MarkNotSet(const QString &text) +inline QString msgMarkNotSet(const QString &text) { - return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text); + return FakeVimHandler::tr("Mark '%1' not set").arg(text); } class Input @@ -294,7 +348,7 @@ public: explicit Input(QChar x) : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {} - Input(int k, int m, QString t) + Input(int k, int m, const QString &t) : m_key(k), m_modifiers(m), m_text(t) { // m_xkey is only a cache. @@ -305,11 +359,28 @@ public: { return m_xkey >= '0' && m_xkey <= '9'; } + bool isKey(int c) const { return !m_modifiers && m_key == c; } + bool isBackspace() const + { + return m_key == Key_Backspace || isControl('h'); + } + + bool isReturn() const + { + return m_key == Key_Return; + } + + bool isEscape() const + { + return isKey(Key_Escape) || isKey(27) || isControl('c') + || isControl(Key_BracketLeft); + } + bool is(int c) const { return m_xkey == c && (m_modifiers == 0 || m_modifiers == Qt::ShiftModifier); @@ -328,13 +399,26 @@ public: bool operator==(const Input &a) const { - return a.m_key == m_key && m_text == a.m_text; + return a.m_key == m_key && a.m_modifiers == m_modifiers + && m_text == a.m_text; } + bool operator!=(const Input &a) const { return !operator==(a); } + QString text() const { return m_text; } + QChar asChar() const + { + return (m_text.size() == 1 ? m_text.at(0) : QChar()); + } + int key() const { return m_key; } + QDebug dump(QDebug ts) const + { + return ts << m_key << '-' << m_modifiers << '-' + << quoteUnprintable(m_text); + } private: int m_key; int m_xkey; @@ -342,10 +426,68 @@ private: QString m_text; }; -typedef QVector<Input> Inputs; +QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); } + +class Inputs : public QVector<Input> +{ +public: + Inputs() {} + explicit Inputs(const QString &str) { parseFrom(str); } + void parseFrom(const QString &str); +}; + + +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, c5 = 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 (i + 5 < n) + c5 = str.at(i + 5).unicode(); + if (c0 == '<') { + if ((c1 == 'C' || c1 == 'c') && c2 == '-' && c4 == '>') { + uint c = (c3 < 90 ? c3 : c3 - 32); + append(Input(c, Qt::ControlModifier, QString(QChar(c - 64)))); + i += 4; + } else { + append(Input(QLatin1Char(c0))); + } + } else { + append(Input(QLatin1Char(c0))); + } + } +} + +class History +{ +public: + History() : m_index(0) {} + void append(const QString &item) + { //qDebug() << "APP: " << item << m_items; + m_items.removeAll(item); + m_items.append(item); m_index = m_items.size() - 1; } + void down() { m_index = qMin(m_index + 1, m_items.size()); } + void up() { m_index = qMax(m_index - 1, 0); } + //void clear() { m_items.clear(); m_index = 0; } + void restart() { m_index = m_items.size(); } + QString current() const { return m_items.value(m_index, QString()); } + QStringList items() const { return m_items; } +private: + QStringList m_items; + int m_index; +}; + // Mappings for a specific mode. -class ModeMapping : private QList<QPair<Inputs, Inputs> > +class ModeMapping : public QList<QPair<Inputs, Inputs> > { public: ModeMapping() { test(); } @@ -375,24 +517,24 @@ public: } } - // Returns 'false' if more input input is needed to decide whether a - // mapping needs to be applied. If a decision can be made, return 'true', + // 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 *input) const + bool mappingDone(Inputs *inputs) const { - Q_UNUSED(input); // FIXME: inefficient. for (int i = 0; i != size(); ++i) { + const Inputs &haystack = at(i).first; // A mapping - if (startsWith(at(i).first, *input)) { - if (at(i).first.size() != input->size()) + if (startsWith(haystack, *inputs)) { + if (haystack.size() != inputs->size()) return false; // This can be extended. // Actual mapping. - *input = at(i).second; + *inputs = at(i).second; return true; } } - // No extensible mapping found. Use input as-is. + // No extensible mapping found. Use inputs as-is. return true; } @@ -403,13 +545,14 @@ private: if (needle.size() > haystack.size()) return false; for (int i = 0; i != needle.size(); ++i) { - if (needle.at(i).text() != haystack.at(i).text()) + if (needle.at(i) != haystack.at(i)) return false; } return true; } }; + class FakeVimHandler::Private : public QObject { Q_OBJECT @@ -422,7 +565,7 @@ public: void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand void handleExCommand(const QString &cmd); - // updates marks positions by the difference in positionChange + // Updates marks positions by the difference in positionChange. void fixMarks(int positionAction, int positionChange); void installEventFilter(); @@ -436,14 +579,16 @@ public: EventResult handleKey(const Input &); Q_SLOT EventResult handleKey2(); EventResult handleInsertMode(const Input &); + EventResult handleReplaceMode(const Input &); EventResult handleCommandMode(const Input &); EventResult handleRegisterMode(const Input &); - EventResult handleMiniBufferModes(const Input &); + EventResult handleExMode(const Input &); + EventResult handleSearchSubSubMode(const Input &); EventResult handleCommandSubSubMode(const Input &); void finishMovement(const QString &dotCommand = QString()); void finishMovement(const QString &dotCommand, int count); void resetCommandMode(); - void search(const QString &needle, bool forward, bool incSearch = false); + void search(const SearchData &sd); void highlightMatches(const QString &needle); void stopIncrementalFind(); @@ -455,18 +600,18 @@ public: bool atEndOfLine() const { return m_tc.atBlockEnd() && m_tc.block().length() > 1; } - int lastPositionInDocument() const; // last valid pos in doc + int lastPositionInDocument() const; // Returns last valid position in doc. int firstPositionInLine(int line) const; // 1 based line, 0 based pos int lastPositionInLine(int line) const; // 1 based line, 0 based pos int lineForPosition(int pos) const; // 1 based line, 0 based pos QString lineContents(int line) const; // 1 based line - void setLineContents(int line, const QString &contents) const; // 1 based line + void setLineContents(int line, const QString &contents); // 1 based line int linesOnScreen() const; int columnsOnScreen() const; int linesInDocument() const; - // all zero-based counting + // The following use all zero-based counting. int cursorLineOnScreen() const; int cursorLineInDocument() const; int physicalCursorColumnInDocument() const; // as stored in the data @@ -482,7 +627,7 @@ public: void setCursorPosition(const CursorPosition &p) { setPosition(p.position); scrollToLineInDocument(p.scrollLine); } - // helper functions for indenting + // Helper functions for indenting/ bool isElectricCharacter(QChar c) const; void indentSelectedText(QChar lastTyped = QChar()); int indentText(const Range &range, QChar lastTyped = QChar()); @@ -500,7 +645,7 @@ public: void moveToMatchingParanthesis(); void moveToWordBoundary(bool simple, bool forward, bool changeWord = false); - // to reduce line noise + // Convenience wrappers to reduce line noise. void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); } void moveToStartOfLine(); void moveToEndOfLine(); @@ -515,11 +660,11 @@ public: bool handleFfTt(QString key); - // helper function for handleExCommand. return 1 based line index. + // Helper function for handleExCommand returning 1 based line index. int readLineCode(QString &cmd); - void selectRange(int beginLine, int endLine); void enterInsertMode(); + void enterReplaceMode(); void enterCommandMode(); void enterExMode(); void showRedMessage(const QString &msg); @@ -527,20 +672,17 @@ public: void notImplementedYet(); void updateMiniBuffer(); void updateSelection(); + void updateCursor(); QWidget *editor() const; QChar characterAtCursor() const { return m_tc.document()->characterAt(m_tc.position()); } void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); } void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); } void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); } - void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); } - - // this asks the layer above (e.g. the fake vim plugin or the - // stand-alone test application to handle the command) - void passUnknownExCommand(const QString &cmd); - // this asks the layer above (e.g. the fake vim plugin or the - // stand-alone test application to handle the set command) - void passUnknownSetCommand(const QString &cmd); + void joinPreviousEditBlock() { UNDO_DEBUG("JOIN"); m_tc.joinPreviousEditBlock(); } + void breakEditBlock() + { m_tc.beginEditBlock(); m_tc.insertText("x"); + m_tc.deletePreviousChar(); m_tc.endEditBlock(); } bool isVisualMode() const { return m_visualMode != NoVisualMode; } bool isNoVisualMode() const { return m_visualMode == NoVisualMode; } @@ -556,6 +698,8 @@ public: void selectBlockTextObject(bool inner, char left, char right); void selectQuotedStringTextObject(bool inner, int type); + Q_SLOT void importSelection(); + public: QTextEdit *m_textedit; QPlainTextEdit *m_plaintextedit; @@ -570,7 +714,6 @@ public: QTextCursor m_tc; int m_oldPosition; // copy from last event to check for external changes int m_anchor; - static QHash<int, Register> m_registers; int m_register; QString m_mvcount; QString m_opcount; @@ -582,10 +725,9 @@ public: bool m_anchorPastEnd; bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol - bool isSearchMode() const - { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; } int m_gflag; // whether current command started with 'g' + QString m_commandPrefix; QString m_commandBuffer; QString m_currentFileName; QString m_currentMessage; @@ -593,34 +735,44 @@ public: bool m_lastSearchForward; bool m_findPending; QString m_lastInsertion; + QString m_lastDeletion; int anchor() const { return m_anchor; } int position() const { return m_tc.position(); } - typedef void (FakeVimHandler::Private::*Transformation)(int, QTextCursor *); - void transformText(const Range &range, Transformation transformation); - - void removeSelectedText(); + struct TransformationData + { + TransformationData(const QString &s, const QVariant &d) + : from(s), extraData(d) {} + QString from; + QString to; + QVariant extraData; + }; + typedef void (Private::*Transformation)(TransformationData *td); + void transformText(const Range &range, Transformation transformation, + const QVariant &extraData = QVariant()); + + void insertText(const Register ®); void removeText(const Range &range); - void removeTransform(int, QTextCursor *); + void removeTransform(TransformationData *td); - void invertCaseSelectedText(); - void invertCaseTransform(int, QTextCursor *); + void invertCase(const Range &range); + void invertCaseTransform(TransformationData *td); - void upCaseSelectedText(); - void upCaseTransform(int, QTextCursor *); + void upCase(const Range &range); + void upCaseTransform(TransformationData *td); - void downCaseSelectedText(); - void downCaseTransform(int, QTextCursor *); + void downCase(const Range &range); + void downCaseTransform(TransformationData *td); - QChar m_replacingCharacter; - void replaceSelectedText(); // replace each character with m_replacingCharacter - void replaceTransform(int, QTextCursor *); + void replaceText(const Range &range, const QString &str); + void replaceByStringTransform(TransformationData *td); + void replaceByCharTransform(TransformationData *td); - QString selectedText() const { return text(Range(position(), anchor())); } - QString text(const Range &range) const; + QString selectText(const Range &range) const; + void setCurrentRange(const Range &range); + Range currentRange() const { return Range(position(), anchor(), m_rangemode); } - void yankSelectedText(); void yankText(const Range &range, int toregister = '"'); void pasteText(bool afterCursor); @@ -630,37 +782,26 @@ public: void redo(); void setUndoPosition(int pos); QMap<int, int> m_undoCursorPosition; // revision -> position - bool m_beginEditBlock; // extra data for '.' void replay(const QString &text, int count); - void setDotCommand(const QString &cmd) { m_dotCommand = cmd; } - void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); } - QString m_dotCommand; - bool m_inReplay; // true if we are executing a '.' + void setDotCommand(const QString &cmd) { g.dotCommand = cmd; } + void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); } // extra data for ';' QString m_semicolonCount; Input m_semicolonType; // 'f', 'F', 't', 'T' QString m_semicolonKey; - // history for '/' - QString lastSearchString() const; - static QStringList m_searchHistory; - int m_searchHistoryIndex; - - // history for ':' - static QStringList m_commandHistory; - int m_commandHistoryIndex; - // visual line mode void enterVisualMode(VisualMode visualMode); void leaveVisualMode(); VisualMode m_visualMode; // marks as lines + int mark(int code) const; + void setMark(int code, int position); QHash<int, int> m_marks; - QString m_oldNeedle; // vi style configuration QVariant config(int code) const { return theFakeVimSetting(code)->value(); } @@ -670,7 +811,6 @@ public: int m_targetColumn; // -1 if past end of line int m_visualTargetColumn; // 'l' can move past eol in visual mode only - int m_cursorWidth; // auto-indent @@ -683,46 +823,67 @@ public: void handleStartOfLine(); void recordJump(); - void recordNewUndo(); QVector<CursorPosition> m_jumpListUndo; QVector<CursorPosition> m_jumpListRedo; QList<QTextEdit::ExtraSelection> m_searchSelections; + QTextCursor m_searchCursor; + QString m_oldNeedle; - bool handleExCommandHelper(const QString &cmd); // Returns success. - QString extractCommand(const QString &line, int *beginLine, int *endLine); - bool handleExBangCommand(const QString &line); - bool handleExDeleteCommand(const QString &line); - bool handleExGotoCommand(const QString &line); - bool handleExHistoryCommand(const QString &line); - bool handleExMapCommand(const QString &line); - bool handleExNormalCommand(const QString &line); - bool handleExReadCommand(const QString &line); - bool handleExRedoCommand(const QString &line); - bool handleExSetCommand(const QString &line); - bool handleExShiftRightCommand(const QString &line); - bool handleExSourceCommand(const QString &line); - bool handleExSubstituteCommand(const QString &line); - bool handleExWriteCommand(const QString &line); - - // All mappings. - typedef QHash<char, ModeMapping> Mappings; - static Mappings m_mappings; - - QVector<Input> m_pendingInput; + bool handleExCommandHelper(const ExCommand &cmd); // Returns success. + bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin? + bool handleExBangCommand(const ExCommand &cmd); + bool handleExDeleteCommand(const ExCommand &cmd); + bool handleExGotoCommand(const ExCommand &cmd); + bool handleExHistoryCommand(const ExCommand &cmd); + bool handleExRegisterCommand(const ExCommand &cmd); + bool handleExMapCommand(const ExCommand &cmd); + bool handleExNormalCommand(const ExCommand &cmd); + bool handleExReadCommand(const ExCommand &cmd); + bool handleExRedoCommand(const ExCommand &cmd); + bool handleExSetCommand(const ExCommand &cmd); + bool handleExShiftCommand(const ExCommand &cmd); + bool handleExSourceCommand(const ExCommand &cmd); + bool handleExSubstituteCommand(const ExCommand &cmd); + bool handleExWriteCommand(const ExCommand &cmd); void timerEvent(QTimerEvent *ev); - int m_inputTimer; void setupCharClass(); int charClass(QChar c, bool simple) const; signed char m_charClass[256]; + + static struct GlobalData + { + GlobalData() + { + inReplay = false; + inputTimer = -1; + } + + // Input. + Inputs pendingInput; + int inputTimer; + + // Repetition. + QString dotCommand; + bool inReplay; // true if we are executing a '.' + + // History for searches. + History searchHistory; + + // History for :ex commands. + History commandHistory; + + QHash<int, Register> registers; + + // All mappings. + typedef QHash<char, ModeMapping> Mappings; + Mappings mappings; + } g; }; -QStringList FakeVimHandler::Private::m_searchHistory; -QStringList FakeVimHandler::Private::m_commandHistory; -QHash<int, Register> FakeVimHandler::Private::m_registers; -FakeVimHandler::Private::Mappings FakeVimHandler::Private::m_mappings; +FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g; FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget) { @@ -752,11 +913,8 @@ void FakeVimHandler::Private::init() m_movetype = MoveInclusive; m_anchor = 0; m_cursorWidth = EDITOR(cursorWidth()); - m_inReplay = false; m_justAutoIndented = 0; m_rangemode = RangeCharMode; - m_beginEditBlock = true; - m_inputTimer = -1; setupCharClass(); } @@ -768,6 +926,8 @@ bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev) KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing); if (key == Key_Escape) { + if (m_subsubmode == SearchSubSubMode) + return true; // Not sure this feels good. People often hit Esc several times if (isNoVisualMode() && m_mode == CommandMode) return false; @@ -830,7 +990,7 @@ EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev) // Try to compensate for code completion if (dist > 0 && dist <= physicalCursorColumnInDocument()) { Range range(m_oldPosition, m_tc.position()); - m_lastInsertion.append(text(range)); + m_lastInsertion.append(selectText(range)); } } else if (!isVisualMode()) { if (atEndOfLine()) @@ -851,8 +1011,8 @@ EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev) // key = shift(key); //} - QTC_ASSERT( - !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1), + QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode + || !m_tc.atBlockEnd() || m_tc.block().length() <= 1, qDebug() << "Cursor at EOL before key handler"); EventResult result = handleKey(Input(key, mods, ev->text())); @@ -862,8 +1022,8 @@ EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev) // We fake vi-style end-of-line behaviour m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode(); - QTC_ASSERT( - !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1), + QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode + || !m_tc.atBlockEnd() || m_tc.block().length() <= 1, qDebug() << "Cursor at EOL after key handler"); if (m_fakeEnd) @@ -872,43 +1032,65 @@ EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev) EDITOR(setTextCursor(m_tc)); m_oldPosition = m_tc.position(); } + + if (hasConfig(ConfigShowMarks)) + updateSelection(); + return result; } void FakeVimHandler::Private::installEventFilter() { + EDITOR(viewport()->installEventFilter(q)); EDITOR(installEventFilter(q)); } void FakeVimHandler::Private::setupWidget() { enterCommandMode(); - //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x'))); if (m_textedit) { m_textedit->setLineWrapMode(QTextEdit::NoWrap); } else if (m_plaintextedit) { m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap); } m_wasReadOnly = EDITOR(isReadOnly()); - //EDITOR(setReadOnly(true)); updateEditor(); + importSelection(); + updateMiniBuffer(); + updateCursor(); +} +void FakeVimHandler::Private::importSelection() +{ QTextCursor tc = EDITOR(textCursor()); + int pos = tc.position(); + int anc = tc.anchor(); if (tc.hasSelection()) { - int pos = tc.position(); - int anc = tc.anchor(); - m_marks['<'] = anc; - m_marks['>'] = pos; - m_anchor = anc; - m_visualMode = VisualCharMode; - tc.clearSelection(); - EDITOR(setTextCursor(tc)); - m_tc = tc; // needed in updateSelection - updateSelection(); + // FIXME: Why? + if (pos < anc) + --anc; + else + tc.movePosition(Left, KeepAnchor); } - - updateMiniBuffer(); + setMark('<', anc); + setMark('>', pos); + m_anchor = anc; + Qt::KeyboardModifiers mods = QApplication::keyboardModifiers(); + if (!tc.hasSelection()) + m_visualMode = NoVisualMode; + else if (mods & Qt::ControlModifier) + m_visualMode = VisualBlockMode; + else if (mods & Qt::AltModifier) + m_visualMode = VisualBlockMode; + else if (mods & Qt::ShiftModifier) + m_visualMode = VisualLineMode; + else + m_visualMode = VisualCharMode; + m_tc = tc; // needed in updateSelection + tc.clearSelection(); + EDITOR(setTextCursor(tc)); + updateSelection(); } void FakeVimHandler::Private::updateEditor() @@ -924,45 +1106,50 @@ void FakeVimHandler::Private::restoreWidget(int tabSize) //showBlackMessage(QString()); //updateMiniBuffer(); //EDITOR(removeEventFilter(q)); - EDITOR(setReadOnly(m_wasReadOnly)); - EDITOR(setCursorWidth(m_cursorWidth)); - EDITOR(setOverwriteMode(false)); + //EDITOR(setReadOnly(m_wasReadOnly)); const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' ')); EDITOR(setTabStopWidth(charWidth * tabSize)); if (isVisualLineMode()) { m_tc = EDITOR(textCursor()); - int beginLine = lineForPosition(m_marks['<']); - int endLine = lineForPosition(m_marks['>']); + int beginLine = lineForPosition(mark('<')); + int endLine = lineForPosition(mark('>')); m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor); m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor); EDITOR(setTextCursor(m_tc)); - } else if (isVisualCharMode()) { + } else if (isVisualCharMode() || isVisualBlockMode()) { m_tc = EDITOR(textCursor()); - m_tc.setPosition(m_marks['<'], MoveAnchor); - m_tc.setPosition(m_marks['>'], KeepAnchor); + m_tc.setPosition(mark('<'), MoveAnchor); + m_tc.setPosition(mark('>'), KeepAnchor); EDITOR(setTextCursor(m_tc)); } m_visualMode = NoVisualMode; + // Force "ordinary" cursor. + m_mode = InsertMode; + m_submode = NoSubMode; + m_subsubmode = NoSubSubMode; + updateCursor(); updateSelection(); } EventResult FakeVimHandler::Private::handleKey(const Input &input) { - if (m_mode == InsertMode || m_mode == CommandMode) { - m_pendingInput.append(input); + KEY_DEBUG("HANDLE INPUT: " << input); + 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 (m_mappings[code].mappingDone(&m_pendingInput)) + if (g.mappings[code].mappingDone(&g.pendingInput)) return handleKey2(); - if (m_inputTimer != -1) - killTimer(m_inputTimer); - m_inputTimer = startTimer(1000); + if (g.inputTimer != -1) + killTimer(g.inputTimer); + g.inputTimer = startTimer(1000); return EventHandled; } - if (m_mode == ExMode || m_mode == SearchForwardMode - || m_mode == SearchBackwardMode) - return handleMiniBufferModes(input); return EventUnhandled; } @@ -971,22 +1158,32 @@ EventResult FakeVimHandler::Private::handleKey2() setUndoPosition(m_tc.position()); if (m_mode == InsertMode) { EventResult result = EventUnhandled; - foreach (const Input &in, m_pendingInput) { + foreach (const Input &in, g.pendingInput) { EventResult r = handleInsertMode(in); if (r == EventHandled) result = EventHandled; } - m_pendingInput.clear(); + g.pendingInput.clear(); + return result; + } + if (m_mode == ReplaceMode) { + EventResult result = EventUnhandled; + foreach (const Input &in, g.pendingInput) { + EventResult r = handleReplaceMode(in); + if (r == EventHandled) + result = EventHandled; + } + g.pendingInput.clear(); return result; } if (m_mode == CommandMode) { EventResult result = EventUnhandled; - foreach (const Input &in, m_pendingInput) { + foreach (const Input &in, g.pendingInput) { EventResult r = handleCommandMode(in); if (r == EventHandled) result = EventHandled; } - m_pendingInput.clear(); + g.pendingInput.clear(); return result; } return EventUnhandled; @@ -1020,7 +1217,7 @@ void FakeVimHandler::Private::setAnchor() if (!isVisualMode()) { m_anchor = m_tc.position(); } else { - // m_marks['<'] = m_tc.position(); + // setMark('<', m_tc.position()); } } @@ -1085,14 +1282,14 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) enterExMode(); m_currentMessage.clear(); m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine)); - m_commandHistory.append(QString()); - m_commandHistoryIndex = m_commandHistory.size() - 1; + //g.commandHistory.append(QString()); updateMiniBuffer(); + updateCursor(); return; } if (isVisualMode()) - m_marks['>'] = m_tc.position(); + setMark('>', m_tc.position()); if (m_submode == ChangeSubMode || m_submode == DeleteSubMode @@ -1107,7 +1304,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) if (m_movetype == MoveInclusive) { if (anchor() <= position()) { - if ( !m_tc.atBlockEnd()) + if (!m_tc.atBlockEnd()) moveRight(); // correction } else { m_anchor++; @@ -1124,9 +1321,9 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) } if (m_submode != TransformSubMode) { - yankSelectedText(); + yankText(currentRange(), m_register); if (m_movetype == MoveLineWise) - m_registers[m_register].rangemode = RangeLineMode; + g.registers[m_register].rangemode = RangeLineMode; } m_positionPastEnd = m_anchorPastEnd = false; @@ -1135,17 +1332,17 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) if (m_submode == ChangeSubMode) { if (m_rangemode == RangeLineMode) m_rangemode = RangeLineModeExclusive; - removeSelectedText(); + removeText(currentRange()); if (!dotCommand.isEmpty()) setDotCommand(QLatin1Char('c') + dotCommand); if (m_movetype == MoveLineWise) insertAutomaticIndentation(true); endEditBlock(); + setUndoPosition(position()); enterInsertMode(); - m_beginEditBlock = false; m_submode = NoSubMode; } else if (m_submode == DeleteSubMode) { - removeSelectedText(); + removeText(currentRange()); if (!dotCommand.isEmpty()) setDotCommand(QLatin1Char('d') + dotCommand); if (m_movetype == MoveLineWise) @@ -1161,7 +1358,7 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) const int la = lineForPosition(anchor()); const int lp = lineForPosition(position()); if (m_register != '"') { - setPosition(m_marks[m_register]); + setPosition(mark(m_register)); moveToStartOfLine(); } else { if (anchor() <= position()) @@ -1171,21 +1368,17 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) showBlackMessage(QString("%1 lines yanked").arg(qAbs(la - lp) + 1)); } else if (m_submode == TransformSubMode) { if (m_subsubmode == InvertCaseSubSubMode) { - invertCaseSelectedText(); + invertCase(currentRange()); if (!dotCommand.isEmpty()) setDotCommand(QLatin1Char('~') + dotCommand); } else if (m_subsubmode == UpCaseSubSubMode) { - upCaseSelectedText(); + upCase(currentRange()); if (!dotCommand.isEmpty()) setDotCommand("gU" + dotCommand); } else if (m_subsubmode == DownCaseSubSubMode) { - downCaseSelectedText(); + downCase(currentRange()); if (!dotCommand.isEmpty()) setDotCommand("gu" + dotCommand); - } else if (m_subsubmode == ReplaceSubSubMode) { - replaceSelectedText(); - if (!dotCommand.isEmpty()) - setDotCommand("r" + dotCommand); } m_submode = NoSubMode; m_subsubmode = NoSubSubMode; @@ -1193,28 +1386,26 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommand) if (m_movetype == MoveLineWise) handleStartOfLine(); endEditBlock(); - } else if (m_submode == ReplaceSubMode) { - m_submode = NoSubMode; } else if (m_submode == IndentSubMode) { recordJump(); beginEditBlock(); indentSelectedText(); endEditBlock(); m_submode = NoSubMode; - updateMiniBuffer(); } else if (m_submode == ShiftRightSubMode) { recordJump(); shiftRegionRight(1); m_submode = NoSubMode; - updateMiniBuffer(); } else if (m_submode == ShiftLeftSubMode) { recordJump(); shiftRegionLeft(1); m_submode = NoSubMode; - updateMiniBuffer(); } resetCommandMode(); + updateSelection(); + updateMiniBuffer(); + updateCursor(); } void FakeVimHandler::Private::resetCommandMode() @@ -1226,14 +1417,19 @@ void FakeVimHandler::Private::resetCommandMode() m_register = '"'; m_tc.clearSelection(); m_rangemode = RangeCharMode; - - updateSelection(); - updateMiniBuffer(); } void FakeVimHandler::Private::updateSelection() { QList<QTextEdit::ExtraSelection> selections = m_searchSelections; + if (!m_searchCursor.isNull()) { + QTextEdit::ExtraSelection sel; + sel.cursor = m_searchCursor; + sel.format = m_searchCursor.blockCharFormat(); + sel.format.setForeground(Qt::white); + sel.format.setBackground(Qt::black); + selections.append(sel); + } if (isVisualMode()) { QTextEdit::ExtraSelection sel; sel.cursor = m_tc; @@ -1246,7 +1442,7 @@ void FakeVimHandler::Private::updateSelection() sel.format.setBackground(Qt::black); #endif const int cursorPos = m_tc.position(); - const int anchorPos = m_marks['<']; + const int anchorPos = mark('<'); //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos; if (isVisualCharMode()) { sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor); @@ -1287,6 +1483,19 @@ void FakeVimHandler::Private::updateSelection() } } //qDebug() << "SELECTION: " << selections; + if (hasConfig(ConfigShowMarks)) { + for (QHashIterator<int, int> it(m_marks); it.hasNext(); ) { + it.next(); + QTextEdit::ExtraSelection sel; + sel.cursor = m_tc; + sel.cursor.setPosition(it.value(), MoveAnchor); + sel.cursor.setPosition(it.value() + 1, KeepAnchor); + sel.format = m_tc.blockCharFormat(); + sel.format.setForeground(Qt::blue); + sel.format.setBackground(Qt::green); + selections.append(sel); + } + } emit q->selectionChanged(selections); } @@ -1309,17 +1518,13 @@ void FakeVimHandler::Private::updateMiniBuffer() msg = "-- VISUAL BLOCK --"; } } else if (m_mode == InsertMode) { - if (m_submode == ReplaceSubMode) - msg = "-- REPLACE --"; - else - msg = "-- INSERT --"; - } else { - if (m_mode == SearchForwardMode) - msg += '/'; - else if (m_mode == SearchBackwardMode) - msg += '?'; - else if (m_mode == ExMode) - msg += ':'; + msg = "-- INSERT --"; + } else if (m_mode == ReplaceMode) { + msg = "-- REPLACE --"; + } else if (!m_commandPrefix.isEmpty()) { + //QTC_ASSERT(m_mode == ExMode || m_subsubmode == SearchSubSubMode, + // qDebug() << "MODE: " << m_mode << m_subsubmode); + msg = m_commandPrefix; foreach (QChar c, m_commandBuffer) { if (c.unicode() < 32) { msg += '^'; @@ -1330,6 +1535,9 @@ void FakeVimHandler::Private::updateMiniBuffer() } if (!msg.isEmpty() && m_mode != CommandMode) msg += QChar(10073); // '|'; // FIXME: Use a real "cursor" + } else { + QTC_ASSERT(m_mode == CommandMode && m_subsubmode != SearchSubSubMode, /**/); + msg = "-- COMMAND --"; } emit q->commandBufferChanged(msg); @@ -1337,7 +1545,8 @@ void FakeVimHandler::Private::updateMiniBuffer() int linesInDoc = linesInDocument(); int l = cursorLineInDocument(); QString status; - const QString pos = QString::fromLatin1("%1,%2").arg(l + 1).arg(physicalCursorColumnInDocument() + 1); + const QString pos = QString::fromLatin1("%1,%2") + .arg(l + 1).arg(physicalCursorColumnInDocument() + 1); // FIXME: physical "-" logical if (linesInDoc != 0) { status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4); @@ -1422,28 +1631,19 @@ EventResult FakeVimHandler::Private::handleCommandSubSubMode(const Input &input) selectQuotedStringTextObject(m_subsubdata.is('i'), input.key()); m_subsubmode = NoSubSubMode; finishMovement(); - } else if (m_submode == TransformSubMode && m_subsubmode == ReplaceSubSubMode) { - if (isVisualLineMode()) - m_rangemode = RangeLineMode; - else if (isVisualBlockMode()) - m_rangemode = RangeBlockMode; - if (!input.text().isEmpty() && input.text().at(0).isPrint()) { - leaveVisualMode(); - m_replacingCharacter = input.text().at(0); - finishMovement(); - } } else if (m_subsubmode == MarkSubSubMode) { - m_marks[input.key()] = m_tc.position(); + setMark(input.asChar().unicode(), m_tc.position()); m_subsubmode = NoSubSubMode; } else if (m_subsubmode == BackTickSubSubMode || m_subsubmode == TickSubSubMode) { - if (m_marks.contains(input.key())) { - setPosition(m_marks[input.key()]); + int m = mark(input.asChar().unicode()); + if (m != -1) { + setPosition(m); if (m_subsubmode == TickSubSubMode) moveToFirstNonBlankOnLine(); finishMovement(); } else { - showRedMessage(msgE20MarkNotSet(input.text())); + showRedMessage(msgMarkNotSet(input.text())); } m_subsubmode = NoSubSubMode; } else { @@ -1456,7 +1656,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) { EventResult handled = EventHandled; - if (input.isKey(Key_Escape) || input.isControl(Key_BracketLeft)) { + if (input.isEscape()) { if (isVisualMode()) { leaveVisualMode(); } else if (m_submode != NoSubMode) { @@ -1465,6 +1665,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) finishMovement(); } else { resetCommandMode(); + updateSelection(); + updateMiniBuffer(); } } else if (m_subsubmode != NoSubSubMode) { handleCommandSubSubMode(input); @@ -1472,9 +1674,33 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) emit q->windowCommandRequested(input.key()); m_submode = NoSubMode; } else if (m_submode == RegisterSubMode) { - m_register = input.key(); + m_register = input.asChar().unicode(); m_submode = NoSubMode; m_rangemode = RangeLineMode; + } else if (m_submode == ReplaceSubMode) { + if (isVisualMode()) { + if (isVisualLineMode()) + m_rangemode = RangeLineMode; + else if (isVisualBlockMode()) + m_rangemode = RangeBlockMode; + else + m_rangemode = RangeCharMode; + leaveVisualMode(); + Range range = currentRange(); + Transformation tr = + &FakeVimHandler::Private::replaceByCharTransform; + transformText(range, tr, input.asChar()); + setPosition(range.beginPos); + } else if (count() <= rightDist()) { + setAnchor(); + moveRight(count()); + replaceText(currentRange(), QString(count(), input.asChar())); + moveLeft(); + setTargetColumn(); + setDotCommand("%1r" + input.text(), count()); + } + m_submode = NoSubMode; + finishMovement(); } else if (m_submode == ChangeSubMode && input.is('c')) { // tested moveToStartOfLine(); setAnchor(); @@ -1519,12 +1745,12 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) finishMovement(); } else if (m_submode == ZSubMode) { //qDebug() << "Z_MODE " << cursorLineInDocument() << linesOnScreen(); - if (input.isKey(Key_Return) || input.is('t')) { + if (input.isReturn() || input.is('t')) { // Cursor line to top of window. if (!m_mvcount.isEmpty()) setPosition(firstPositionInLine(count())); scrollUp(- cursorLineOnScreen()); - if (input.isKey(Key_Return)) + if (input.isReturn()) moveToFirstNonBlankOnLine(); finishMovement(); } else if (input.is('.') || input.is('z')) { @@ -1554,23 +1780,6 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) handleExCommand(QString(QLatin1Char('x'))); else if (input.is('Q')) handleExCommand("q!"); - } else if (m_submode == ReplaceSubMode) { - const QString text = input.text(); - if (count() <= (rightDist() + atEndOfLine()) && text.size() == 1 - && (text.at(0).isPrint() || text.at(0).isSpace())) { - if (atEndOfLine()) - moveLeft(); - setAnchor(); - moveRight(count()); - removeSelectedText(); - m_tc.insertText(QString(count(), text.at(0))); - m_movetype = MoveExclusive; - setDotCommand("%1r" + text, count()); - moveLeft(); - } - setTargetColumn(); - m_submode = NoSubMode; - finishMovement(); } else if (input.isDigit()) { if (input.is('0') && m_mvcount.isEmpty()) { m_movetype = MoveExclusive; @@ -1601,18 +1810,18 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) finishMovement(); } else if (input.is(':')) { enterExMode(); + g.commandHistory.restart(); m_currentMessage.clear(); m_commandBuffer.clear(); if (isVisualMode()) m_commandBuffer = "'<,'>"; - m_commandHistory.append(QString()); - m_commandHistoryIndex = m_commandHistory.size() - 1; updateMiniBuffer(); } else if (input.is('/') || input.is('?')) { + m_lastSearchForward = input.is('/'); + g.searchHistory.restart(); if (hasConfig(ConfigUseCoreSearch)) { // re-use the core dialog. m_findPending = true; - m_lastSearchForward = (input.is('/')); EDITOR(setTextCursor(m_tc)); emit q->findRequested(!m_lastSearchForward); m_tc = EDITOR(textCursor()); @@ -1620,25 +1829,35 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else { // FIXME: make core find dialog sufficiently flexible to // produce the "default vi" behaviour too. For now, roll our own. - enterExMode(); // to get the cursor disabled m_currentMessage.clear(); - m_mode = (input.is('/')) ? SearchForwardMode : SearchBackwardMode; - m_commandBuffer.clear(); - m_searchHistory.append(QString()); - m_searchHistoryIndex = m_searchHistory.size() - 1; + m_movetype = MoveExclusive; + m_subsubmode = SearchSubSubMode; + m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?'); + m_commandBuffer = QString(); + updateCursor(); updateMiniBuffer(); } } else if (input.is('`')) { m_subsubmode = BackTickSubSubMode; } else if (input.is('#') || input.is('*')) { // FIXME: That's not proper vim behaviour - m_tc.select(QTextCursor::WordUnderCursor); - QString needle = "\\<" + m_tc.selection().toPlainText() + "\\>"; - m_searchHistory.append(needle); - m_lastSearchForward = (input.is('*')); - updateMiniBuffer(); - search(needle, m_lastSearchForward); - recordJump(); + QTextCursor tc = m_tc; + tc.select(QTextCursor::WordUnderCursor); + QString needle = "\\<" + tc.selection().toPlainText() + "\\>"; + g.searchHistory.append(needle); + m_lastSearchForward = input.is('*'); + m_currentMessage.clear(); + m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?'); + m_commandBuffer = needle; + SearchData sd; + sd.needle = needle; + sd.forward = m_lastSearchForward; + sd.highlightCursor = false; + sd.highlightMatches = true; + search(sd); + //m_searchCursor = QTextCursor(); + //updateSelection(); + //updateMiniBuffer(); } else if (input.is('\'')) { m_subsubmode = TickSubSubMode; } else if (input.is('|')) { @@ -1652,12 +1871,11 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) enterExMode(); m_currentMessage.clear(); m_commandBuffer = "'<,'>!"; - m_commandHistory.append(QString()); - m_commandHistoryIndex = m_commandHistory.size() - 1; + //g.commandHistory.append(QString()); updateMiniBuffer(); } else if (input.is('"')) { m_submode = RegisterSubMode; - } else if (input.isKey(Key_Return)) { + } else if (input.isReturn()) { moveToStartOfLine(); moveDown(); moveToFirstNonBlankOnLine(); @@ -1693,12 +1911,13 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else if (input.is(',')) { passShortcuts(true); } else if (input.is('.')) { - //qDebug() << "REPEATING" << quoteUnprintable(m_dotCommand) << count(); - QString savedCommand = m_dotCommand; - m_dotCommand.clear(); + //qDebug() << "REPEATING" << quoteUnprintable(g.dotCommand) << count() + // << input; + QString savedCommand = g.dotCommand; + g.dotCommand.clear(); replay(savedCommand, count()); enterCommandMode(); - m_dotCommand = savedCommand; + g.dotCommand = savedCommand; } else if (input.is('<') && isNoVisualMode()) { m_submode = ShiftLeftSubMode; } else if (input.is('<') && isVisualMode()) { @@ -1722,12 +1941,16 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) finishMovement(); } else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) { leaveVisualMode(); + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); m_lastInsertion.clear(); if (!atEndOfLine()) moveRight(); updateMiniBuffer(); } else if (input.is('A')) { + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); moveBehindEndOfLine(); setDotCommand(QString(QLatin1Char('A'))); @@ -1791,14 +2014,14 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else if ((input.is('d') || input.is('x')) && isVisualLineMode()) { leaveVisualMode(); m_rangemode = RangeLineMode; - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); handleStartOfLine(); } else if ((input.is('d') || input.is('x')) && isVisualBlockMode()) { leaveVisualMode(); m_rangemode = RangeBlockMode; - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); setPosition(qMin(position(), anchor())); } else if (input.is('D') && isNoVisualMode()) { if (atEndOfLine()) @@ -1815,14 +2038,14 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) leaveVisualMode(); m_rangemode = RangeLineMode; m_submode = NoSubMode; - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); moveToFirstNonBlankOnLine(); } else if ((input.is('D') || input.is('X')) && isVisualBlockMode()) { leaveVisualMode(); m_rangemode = RangeBlockAndTailMode; - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); setPosition(qMin(position(), anchor())); } else if (input.isControl('d')) { int sline = cursorLineOnScreen(); @@ -1874,8 +2097,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) m_tc.setPosition(firstPositionInLine(n), KeepAnchor); } finishMovement(dotCommand); - } else if (input.is('h') || input.isKey(Key_Left) - || input.isKey(Key_Backspace) || input.isControl('h')) { + } else if (input.is('h') || input.isKey(Key_Left) || input.isBackspace()) { m_movetype = MoveExclusive; int n = qMin(count(), leftDist()); if (m_fakeEnd && m_tc.block().length() > 1) @@ -1890,6 +2112,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) finishMovement(); } else if (!isVisualMode() && (input.is('i') || input.isKey(Key_Insert))) { setDotCommand(QString(QLatin1Char('i'))); // setDotCommand("%1i", count()); + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); updateMiniBuffer(); if (atEndOfLine()) @@ -1909,6 +2133,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) m_gflag = false; m_tc.clearSelection(); } + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); } else if (input.isControl('i')) { if (!m_jumpListRedo.isEmpty()) { @@ -1930,12 +2156,12 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) setAnchor(); moveRight(); if (m_gflag) { - removeSelectedText(); + removeText(currentRange()); } else { while (characterAtCursor() == ' ' || characterAtCursor() == '\t') moveRight(); - removeSelectedText(); + removeText(currentRange()); m_tc.insertText(QString(QLatin1Char(' '))); } } @@ -1973,8 +2199,12 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) handleStartOfLine(); finishMovement(); } else if (input.is('n') || input.is('N')) { - search(lastSearchString(), m_lastSearchForward); - recordJump(); + SearchData sd; + sd.needle = g.searchHistory.current(); + sd.forward = input.is('n') ? m_lastSearchForward : !m_lastSearchForward; + sd.highlightCursor = false; + sd.highlightMatches = true; + search(sd); } else if (isVisualMode() && (input.is('o') || input.is('O'))) { int pos = position(); setPosition(anchor()); @@ -1985,20 +2215,28 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) if (m_positionPastEnd) m_visualTargetColumn = -1; updateSelection(); - } else if (input.is('o') || input.is('O')) { - beginEditBlock(); + } else if (input.is('o')) { setDotCommand("%1o", count()); + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); - m_beginEditBlock = false; + beginEditBlock(position()); moveToFirstNonBlankOnLine(); - if (input.is('O')) - moveToStartOfLine(); - else - moveBehindEndOfLine(); - m_tc.insertText("\n"); - if (input.is('O')) - moveUp(); - insertAutomaticIndentation(input.is('o')); + moveBehindEndOfLine(); + insertText(Register("\n")); + insertAutomaticIndentation(true); + endEditBlock(); + } else if (input.is('O')) { + setDotCommand("%1O", count()); + setUndoPosition(position()); + breakEditBlock(); + enterInsertMode(); + beginEditBlock(position()); + moveToFirstNonBlankOnLine(); + moveToStartOfLine(); + insertText(Register("\n")); + moveUp(); + insertAutomaticIndentation(false); endEditBlock(); } else if (input.isControl('o')) { if (!m_jumpListUndo.isEmpty()) { @@ -2011,19 +2249,12 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) setTargetColumn(); setDotCommand("%1p", count()); finishMovement(); - } else if (isVisualMode() && input.is('r')) { - m_submode = TransformSubMode; - m_subsubmode = ReplaceSubSubMode; } else if (input.is('r')) { m_submode = ReplaceSubMode; - setDotCommand(QString(QLatin1Char('r'))); } else if (!isVisualMode() && input.is('R')) { - // FIXME: right now we repeat the insertion count() times, - // but not the deletion - m_lastInsertion.clear(); - enterInsertMode(); - m_submode = ReplaceSubMode; - setDotCommand(QString(QLatin1Char('R'))); + setUndoPosition(position()); + breakEditBlock(); + enterReplaceMode(); updateMiniBuffer(); } else if (input.isControl('r')) { redo(); @@ -2033,11 +2264,13 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) moveLeft(); setAnchor(); moveRight(qMin(count(), rightDist())); - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); setDotCommand("%1s", count()); m_opcount.clear(); m_mvcount.clear(); + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); } else if (input.is('S')) { if (!isVisualMode()) { @@ -2046,8 +2279,9 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) setPosition(lastPositionInLine(line + count() - 1)); } setDotCommand("%1S", count()); + setUndoPosition(position()); + breakEditBlock(); enterInsertMode(); - m_beginEditBlock = false; m_submode = ChangeSubMode; m_movetype = MoveLineWise; finishMovement(); @@ -2116,8 +2350,8 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) if (leftDist() > 0) { setAnchor(); moveLeft(qMin(count(), leftDist())); - yankSelectedText(); - removeSelectedText(); + yankText(currentRange(), m_register); + removeText(currentRange()); } finishMovement(); } else if ((m_submode == YankSubMode && input.is('y')) @@ -2143,14 +2377,14 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) || (input.is('Y') && isVisualLineMode()) || (input.is('Y') && isVisualCharMode())) { m_rangemode = RangeLineMode; - yankSelectedText(); + yankText(currentRange(), m_register); setPosition(qMin(position(), anchor())); moveToStartOfLine(); leaveVisualMode(); finishMovement(); } else if ((input.is('y') || input.is('Y')) && isVisualBlockMode()) { m_rangemode = RangeBlockMode; - yankSelectedText(); + yankText(currentRange(), m_register); setPosition(qMin(position(), anchor())); leaveVisualMode(); finishMovement(); @@ -2159,18 +2393,19 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else if (input.is('Z')) { m_submode = CapitalZSubMode; } else if (!m_gflag && input.is('~') && !isVisualMode()) { + m_movetype = MoveExclusive; if (!atEndOfLine()) { beginEditBlock(); setAnchor(); moveRight(qMin(count(), rightDist())); if (input.is('~')) { - invertCaseSelectedText(); + invertCase(currentRange()); setDotCommand("%1~", count()); } else if (input.is('u')) { - downCaseSelectedText(); + downCase(currentRange()); setDotCommand("%1gu", count()); } else if (input.is('U')) { - upCaseSelectedText(); + upCase(currentRange()); setDotCommand("%1gU", count()); } endEditBlock(); @@ -2180,6 +2415,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) || (m_gflag && input.is('u') && !isVisualMode()) || (m_gflag && input.is('U') && !isVisualMode())) { m_gflag = false; + m_movetype = MoveExclusive; if (atEndOfLine()) moveLeft(); setAnchor(); @@ -2194,6 +2430,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) || (m_gflag && input.is('u') && isVisualMode()) || (m_gflag && input.is('U') && isVisualMode())) { m_gflag = false; + m_movetype = MoveExclusive; if (isVisualLineMode()) m_rangemode = RangeLineMode; else if (isVisualBlockMode()) @@ -2220,7 +2457,7 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) } else if (input.is(Key_Delete)) { setAnchor(); moveRight(qMin(1, rightDist())); - removeSelectedText(); + removeText(currentRange()); } else if (input.is(Key_BracketLeft) || input.is(Key_BracketRight)) { } else if (input.isControl(Key_BracketRight)) { @@ -2241,13 +2478,36 @@ EventResult FakeVimHandler::Private::handleCommandMode(const Input &input) return handled; } +EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) +{ + if (input.isEscape()) { + moveLeft(qMin(1, leftDist())); + setTargetColumn(); + m_submode = NoSubMode; + m_mode = CommandMode; + finishMovement(); + } else { + joinPreviousEditBlock(); + if (!atEndOfLine()) { + setAnchor(); + moveRight(); + m_lastDeletion += selectText(Range(position(), anchor())); + removeText(currentRange()); + } + const QString text = input.text(); + m_lastInsertion += text; + insertText(text); + endEditBlock(); + } + return EventHandled; +} + EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) { //const int key = input.key; //const QString &text = input.text; - if (input.isKey(Key_Escape) || input.isKey(27) || input.isControl('c') || - input.isControl(Key_BracketLeft)) { + if (input.isEscape()) { if (isVisualBlockMode() && !m_lastInsertion.contains('\n')) { leaveVisualMode(); joinPreviousEditBlock(); @@ -2257,7 +2517,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) setTargetColumn(); for (int i = 0; i < m_visualInsertCount; ++i) { moveDown(); - m_tc.insertText(m_lastInsertion); + insertText(m_lastInsertion); } moveLeft(1); Range range(pos, position(), RangeBlockMode); @@ -2266,32 +2526,26 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) setDotCommand("p"); endEditBlock(); } else { - // normal insertion. start with '1', as one instance was - // already physically inserted while typing - QString data = m_lastInsertion; - for (int i = 1; i < count(); ++i) { - m_tc.insertText(m_lastInsertion); + // Normal insertion. Start with '1', as one instance was + // already physically inserted while typing. + QString data; + for (int i = 1; i < count(); ++i) data += m_lastInsertion; - } + insertText(data); moveLeft(qMin(1, leftDist())); setTargetColumn(); leaveVisualMode(); - recordNewUndo(); } - m_dotCommand += m_lastInsertion; - m_dotCommand += QChar(27); + g.dotCommand += m_lastInsertion; + g.dotCommand += QChar(27); enterCommandMode(); m_submode = NoSubMode; } else if (input.isKey(Key_Insert)) { - if (m_submode == ReplaceSubMode) { - EDITOR(setCursorWidth(m_cursorWidth)); - EDITOR(setOverwriteMode(false)); - m_submode = NoSubMode; - } else { - EDITOR(setCursorWidth(m_cursorWidth)); - EDITOR(setOverwriteMode(true)); - m_submode = ReplaceSubMode; - } + if (m_mode == ReplaceMode) + m_mode = InsertMode; + else + m_mode = ReplaceMode; + updateCursor(); } else if (input.isKey(Key_Left)) { moveLeft(count()); setTargetColumn(); @@ -2320,13 +2574,13 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) moveBehindEndOfLine(); setTargetColumn(); m_lastInsertion.clear(); - } else if (input.isKey(Key_Return)) { + } else if (input.isReturn()) { m_submode = NoSubMode; - m_tc.insertBlock(); + insertText(Register("\n")); m_lastInsertion += "\n"; insertAutomaticIndentation(true); setTargetColumn(); - } else if (input.isKey(Key_Backspace) || input.isControl('h')) { + } else if (input.isBackspace()) { joinPreviousEditBlock(); m_justAutoIndented = 0; if (!m_lastInsertion.isEmpty() || hasConfig(ConfigBackspace, "start")) { @@ -2345,6 +2599,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) m_lastInsertion.clear(); // FIXME } else { m_tc.deletePreviousChar(); + fixMarks(position(), -1); m_lastInsertion.chop(1); } setTargetColumn(); @@ -2367,7 +2622,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) const int col = physicalCursorColumnInDocument(); QString str = QString(ts - col % ts, ' '); m_lastInsertion.append(str); - m_tc.insertText(str); + insertText(str); setTargetColumn(); } else if (input.isControl('d')) { // remove one level of indentation from the current line @@ -2390,33 +2645,21 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) //} else if (key >= control('a') && key <= control('z')) { // // ignore these } else if (!input.text().isEmpty()) { - const QString text = input.text(); - if (m_beginEditBlock) { - beginEditBlock(); - m_beginEditBlock = false; - } else { - joinPreviousEditBlock(); - } + joinPreviousEditBlock(); m_justAutoIndented = 0; + const QString text = input.text(); m_lastInsertion.append(text); - if (m_submode == ReplaceSubMode) { - if (atEndOfLine()) - m_submode = NoSubMode; - else - m_tc.deleteChar(); - } - m_tc.insertText(text); + insertText(text); if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) { const QString leftText = m_tc.block().text() .left(m_tc.position() - 1 - m_tc.block().position()); if (leftText.simplified().isEmpty()) { - Range range(position(), position()); - range.rangemode = m_rangemode; + Range range(position(), position(), m_rangemode); indentText(range, text.at(0)); } } - if (!m_inReplay) + if (!g.inReplay) emit q->completionRequested(); setTargetColumn(); endEditBlock(); @@ -2427,15 +2670,15 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) return EventHandled; } -EventResult FakeVimHandler::Private::handleMiniBufferModes(const Input &input) +EventResult FakeVimHandler::Private::handleExMode(const Input &input) { - if (input.isKey(Key_Escape) || input.isControl('c') - || input.isControl(Key_BracketLeft)) { + if (input.isEscape()) { m_commandBuffer.clear(); enterCommandMode(); updateMiniBuffer(); - } else if (input.isKey(Key_Backspace)) { + } else if (input.isBackspace()) { if (m_commandBuffer.isEmpty()) { + m_commandPrefix.clear(); enterCommandMode(); } else { m_commandBuffer.chop(1); @@ -2446,74 +2689,109 @@ EventResult FakeVimHandler::Private::handleMiniBufferModes(const Input &input) if (!m_commandBuffer.isEmpty()) m_commandBuffer.chop(1); updateMiniBuffer(); - } else if (input.isKey(Key_Return) && m_mode == ExMode) { + } else if (input.isReturn()) { if (!m_commandBuffer.isEmpty()) { - m_commandHistory.takeLast(); - m_commandHistory.append(m_commandBuffer); + //g.commandHistory.takeLast(); + g.commandHistory.append(m_commandBuffer); handleExCommand(m_commandBuffer); - if (m_textedit || m_plaintextedit) { + if (m_textedit || m_plaintextedit) leaveVisualMode(); - } } - } else if (input.isKey(Key_Return) && isSearchMode() - && !hasConfig(ConfigIncSearch)) { - if (!m_commandBuffer.isEmpty()) { - m_searchHistory.takeLast(); - m_searchHistory.append(m_commandBuffer); - m_lastSearchForward = (m_mode == SearchForwardMode); - search(lastSearchString(), m_lastSearchForward); - recordJump(); - } - enterCommandMode(); updateMiniBuffer(); - } else if ((input.isKey(Key_Up) || input.isKey(Key_PageUp)) && isSearchMode()) { - // FIXME: This and the three cases below are wrong as vim - // takes only matching entries in the history into account. - if (m_searchHistoryIndex > 0) { - --m_searchHistoryIndex; - showBlackMessage(m_searchHistory.at(m_searchHistoryIndex)); - } - } else if ((input.isKey(Key_Up) || input.isKey(Key_PageUp)) && m_mode == ExMode) { - if (m_commandHistoryIndex > 0) { - --m_commandHistoryIndex; - showBlackMessage(m_commandHistory.at(m_commandHistoryIndex)); - } - } else if ((input.isKey(Key_Down) || input.isKey(Key_PageDown)) && isSearchMode()) { - if (m_searchHistoryIndex < m_searchHistory.size() - 1) { - ++m_searchHistoryIndex; - showBlackMessage(m_searchHistory.at(m_searchHistoryIndex)); - } - } else if ((input.isKey(Key_Down) || input.isKey(Key_PageDown)) && m_mode == ExMode) { - if (m_commandHistoryIndex < m_commandHistory.size() - 1) { - ++m_commandHistoryIndex; - showBlackMessage(m_commandHistory.at(m_commandHistoryIndex)); - } + } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) { + g.commandHistory.up(); + m_commandBuffer = g.commandHistory.current(); + updateMiniBuffer(); + } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) { + g.commandHistory.down(); + m_commandBuffer = g.commandHistory.current(); + updateMiniBuffer(); } else if (input.isKey(Key_Tab)) { m_commandBuffer += QChar(9); updateMiniBuffer(); - } else if (input.isKey(Key_Return) && isSearchMode() - && hasConfig(ConfigIncSearch)) { + } else if (!input.text().isEmpty()) { + m_commandBuffer += input.text(); + updateMiniBuffer(); + } else { + qDebug() << "IGNORED IN EX-MODE: " << input.key() << input.text(); + return EventUnhandled; + } + return EventHandled; +} + +EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input) +{ + if (input.isEscape()) { + m_commandBuffer.clear(); + m_searchCursor = QTextCursor(); + updateSelection(); + enterCommandMode(); + updateMiniBuffer(); + } else if (input.isBackspace()) { + if (m_commandBuffer.isEmpty()) { + m_commandPrefix.clear(); + m_searchCursor = QTextCursor(); + enterCommandMode(); + } else { + m_commandBuffer.chop(1); + } + updateMiniBuffer(); + } else if (input.isKey(Key_Left)) { + if (!m_commandBuffer.isEmpty()) + m_commandBuffer.chop(1); + updateMiniBuffer(); + } else if (input.isReturn()) { + m_searchCursor = QTextCursor(); + QString needle = m_commandBuffer; + if (!needle.isEmpty()) { + g.searchHistory.append(needle); + if (!hasConfig(ConfigIncSearch)) { + SearchData sd; + sd.needle = needle; + sd.forward = m_lastSearchForward; + sd.highlightCursor = false; + sd.highlightMatches = true; + search(sd); + } + finishMovement(m_commandPrefix + needle + "\n"); + } enterCommandMode(); - QString needle = m_commandBuffer.mid(1); // FIXME: why highlightMatches(needle); updateMiniBuffer(); - } else if (isSearchMode() && hasConfig(ConfigIncSearch)) { - m_commandBuffer = m_commandBuffer.mid(1); // FIXME: why - QString needle = m_commandBuffer + input.text(); - search(needle, m_lastSearchForward, true); + } 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. + g.searchHistory.up(); + showBlackMessage(g.searchHistory.current()); + } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) { + g.searchHistory.down(); + showBlackMessage(g.searchHistory.current()); + } else if (input.isKey(Key_Tab)) { + m_commandBuffer += QChar(9); updateMiniBuffer(); - recordJump(); } else if (!input.text().isEmpty()) { m_commandBuffer += input.text(); updateMiniBuffer(); - } else { - qDebug() << "IGNORED IN MINIBUFFER MODE: " << input.key() << input.text(); - return EventUnhandled; } + + if (hasConfig(ConfigIncSearch) && !input.isReturn() && !input.isEscape()) { + SearchData sd; + sd.needle = m_commandBuffer; + sd.forward = m_lastSearchForward; + sd.mustMove = false; + sd.highlightCursor = true; + sd.highlightMatches = false; + search(sd); + } + + //else { + // qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text(); + // return EventUnhandled; + //} return EventHandled; } -// 1 based. +// This uses 1 based line counting. int FakeVimHandler::Private::readLineCode(QString &cmd) { //qDebug() << "CMD: " << cmd; @@ -2521,19 +2799,32 @@ int FakeVimHandler::Private::readLineCode(QString &cmd) return -1; QChar c = cmd.at(0); cmd = cmd.mid(1); - if (c == '.') + if (c == '.') { + if (cmd.isEmpty()) + return cursorLineInDocument() + 1; + QChar c1 = cmd.at(0); + if (c1 == '+' || c1 == '-') { + // Repeat for things like .+4 + cmd = cmd.mid(1); + return cursorLineInDocument() + readLineCode(cmd); + } return cursorLineInDocument() + 1; + } if (c == '$') return linesInDocument(); if (c == '\'' && !cmd.isEmpty()) { - int mark = m_marks.value(cmd.at(0).unicode()); - if (!mark) { - showRedMessage(msgE20MarkNotSet(cmd.at(0))); + if (cmd.isEmpty()) { + showRedMessage(msgMarkNotSet(QString())); + return -1; + } + int m = mark(cmd.at(0).unicode()); + if (m == -1) { + showRedMessage(msgMarkNotSet(cmd.at(0))); cmd = cmd.mid(1); return -1; } cmd = cmd.mid(1); - return lineForPosition(mark); + return lineForPosition(m); } if (c == '-') { int n = readLineCode(cmd); @@ -2544,10 +2835,9 @@ int FakeVimHandler::Private::readLineCode(QString &cmd) return cursorLineInDocument() + 1 + (n == -1 ? 1 : n); } if (c == '\'' && !cmd.isEmpty()) { - int pos = m_marks.value(cmd.at(0).unicode(), -1); - //qDebug() << " MARK: " << cmd.at(0) << pos << lineForPosition(pos); + int pos = mark(cmd.at(0).unicode()); if (pos == -1) { - showRedMessage(msgE20MarkNotSet(cmd.at(0))); + showRedMessage(msgMarkNotSet(cmd.at(0))); cmd = cmd.mid(1); return -1; } @@ -2566,24 +2856,16 @@ int FakeVimHandler::Private::readLineCode(QString &cmd) //qDebug() << "N: " << n; return n; } - // not parsed + // Parsing failed. cmd = c + cmd; return -1; } -void FakeVimHandler::Private::selectRange(int beginLine, int endLine) +void FakeVimHandler::Private::setCurrentRange(const Range &range) { - if (beginLine == -1) - beginLine = cursorLineInDocument(); - if (endLine == -1) - endLine = cursorLineInDocument(); - if (beginLine > endLine) - qSwap(beginLine, endLine); - setAnchor(firstPositionInLine(beginLine)); - if (endLine == linesInDocument()) - setPosition(lastPositionInLine(endLine)); - else - setPosition(firstPositionInLine(endLine + 1)); + setAnchor(range.beginPos); + setPosition(range.endPos); + m_rangemode = range.rangemode; } // use handleExCommand for invoking commands that might move the cursor @@ -2594,71 +2876,46 @@ void FakeVimHandler::Private::handleCommand(const QString &cmd) EDITOR(setTextCursor(m_tc)); } -QString FakeVimHandler::Private::extractCommand(const QString &line, - int *beginLine, int *endLine) -{ - QString cmd = line; - *beginLine = -1; - *endLine = -1; - - // FIXME: that seems to be different for %w and %s - if (cmd.startsWith(QLatin1Char('%'))) - cmd = "1,$" + cmd.mid(1); - - int lineNumber = readLineCode(cmd); - if (lineNumber != -1) - *beginLine = lineNumber; - - if (cmd.startsWith(',')) { - cmd = cmd.mid(1); - lineNumber = readLineCode(cmd); - if (lineNumber != -1) - *endLine = lineNumber; - } - //qDebug() << "RANGE: " << beginLine << endLine << cmd << lineNumber << m_marks; - return cmd; -} - -bool FakeVimHandler::Private::handleExSubstituteCommand(const QString &line) +bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd) // :substitute { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - if (cmd.startsWith(QLatin1String("substitute"))) - cmd = cmd.mid(10); - else if (cmd.startsWith('s') && line.size() > 1 - && !isalpha(cmd.at(1).unicode())) - cmd = cmd.mid(1); + QString line = cmd.cmd + ' ' + cmd.args; + line = line.trimmed(); + if (line.startsWith(_("substitute"))) + line = line.mid(10); + else if (line.startsWith('s') && line.size() > 1 + && !isalpha(line.at(1).unicode())) + line = line.mid(1); else return false; + // we have /{pattern}/{string}/[flags] now - if (cmd.isEmpty()) + if (line.isEmpty()) return false; - const QChar separator = cmd.at(0); + const QChar separator = line.at(0); int pos1 = -1; int pos2 = -1; int i; - for (i = 1; i < cmd.size(); ++i) { - if (cmd.at(i) == separator && cmd.at(i - 1) != '\\') { + for (i = 1; i < line.size(); ++i) { + if (line.at(i) == separator && line.at(i - 1) != '\\') { pos1 = i; break; } } if (pos1 == -1) return false; - for (++i; i < cmd.size(); ++i) { - if (cmd.at(i) == separator && cmd.at(i - 1) != '\\') { + for (++i; i < line.size(); ++i) { + if (line.at(i) == separator && line.at(i - 1) != '\\') { pos2 = i; break; } } if (pos2 == -1) - pos2 = cmd.size(); + pos2 = line.size(); - QString needle = cmd.mid(1, pos1 - 1); - const QString replacement = cmd.mid(pos1 + 1, pos2 - pos1 - 1); - QString flags = cmd.mid(pos2 + 1); + QString needle = line.mid(1, pos1 - 1); + const QString replacement = line.mid(pos1 + 1, pos2 - pos1 - 1); + QString flags = line.mid(pos2 + 1); needle.replace('$', '\n'); needle.replace("\\\n", "\\$"); @@ -2666,6 +2923,8 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const QString &line) if (flags.contains('i')) pattern.setCaseSensitivity(Qt::CaseInsensitive); const bool global = flags.contains('g'); + const int beginLine = lineForPosition(cmd.range.beginPos); + const int endLine = lineForPosition(cmd.range.endPos); beginEditBlock(); for (int line = endLine; line >= beginLine; --line) { QString origText = lineContents(line); @@ -2700,19 +2959,12 @@ bool FakeVimHandler::Private::handleExSubstituteCommand(const QString &line) return true; } -bool FakeVimHandler::Private::handleExMapCommand(const QString &line) // :map +bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map { - int pos1 = line.indexOf(QLatin1Char(' ')); - if (pos1 == -1) - return false; - int pos2 = line.indexOf(QLatin1Char(' '), pos1 + 1); - if (pos2 == -1) - return false; - QByteArray modes; enum Type { Map, Noremap, Unmap } type; - QByteArray cmd = line.left(pos1).toLatin1(); + QByteArray cmd = cmd0.cmd.toLatin1(); // Strange formatting. But everything else is even uglier. if (cmd == "map") { modes = "nvo"; type = Map; } else @@ -2750,45 +3002,48 @@ bool FakeVimHandler::Private::handleExMapCommand(const QString &line) // :map else return false; - QString lhs = line.mid(pos1 + 1, pos2 - pos1 - 1); - QString rhs = line.mid(pos2 + 1); + const int pos = cmd0.args.indexOf(QLatin1Char(' ')); + if (pos == -1) { + // FIXME: Dump mappings here. + //qDebug() << g.mappings; + return true;; + } + + QString lhs = cmd0.args.left(pos); + QString rhs = cmd0.args.mid(pos + 1); Inputs key; - foreach (QChar c, lhs) - key.append(Input(c)); + key.parseFrom(lhs); //qDebug() << "MAPPING: " << modes << lhs << rhs; switch (type) { case Unmap: foreach (char c, modes) - if (m_mappings.contains(c)) - m_mappings[c].remove(key); + if (g.mappings.contains(c)) + g.mappings[c].remove(key); break; case Map: rhs = rhs; // FIXME: expand rhs. // Fall through. case Noremap: { - Inputs inputs; - foreach (QChar c, rhs) - inputs.append(Input(c)); + Inputs inputs(rhs); foreach (char c, modes) - m_mappings[c].insert(key, inputs); + g.mappings[c].insert(key, inputs); break; } } return true; } -bool FakeVimHandler::Private::handleExHistoryCommand(const QString &cmd) // :history +bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd) { - static QRegExp reHistory("^his(tory)?( (.*))?$"); - if (reHistory.indexIn(cmd) == -1) + // :history + if (cmd.cmd != "his" && cmd.cmd != "history") return false; - QString arg = reHistory.cap(3); - if (arg.isEmpty()) { + if (cmd.args.isEmpty()) { QString info; info += "# command history\n"; int i = 0; - foreach (const QString &item, m_commandHistory) { + foreach (const QString &item, g.commandHistory.items()) { ++i; info += QString("%1 %2\n").arg(i, -8).arg(item); } @@ -2800,164 +3055,176 @@ bool FakeVimHandler::Private::handleExHistoryCommand(const QString &cmd) // :his return true; } -bool FakeVimHandler::Private::handleExSetCommand(const QString &cmd) // :set +bool FakeVimHandler::Private::handleExRegisterCommand(const ExCommand &cmd) +{ + // :reg and :di[splay] + if (cmd.cmd != "reg" && cmd.cmd != "registers" + && cmd.cmd != "di" && cmd.cmd != "display") + return false; + + QByteArray regs = cmd.args.toLatin1(); + if (regs.isEmpty()) { + regs = "\"0123456789"; + QHashIterator<int, Register> it(g.registers); + while (it.hasNext()) { + it.next(); + if (it.key() > '9') + regs += char(it.key()); + } + } + QString info; + info += "--- Registers ---\n"; + foreach (char reg, regs) { + QString value = quoteUnprintable(g.registers[reg].contents); + info += QString("\"%1 %2\n").arg(reg).arg(value); + } + emit q->extraInformationChanged(info); + updateMiniBuffer(); + return true; +} + +bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd) { - static QRegExp reSet("^set?( (.*))?$"); - if (reSet.indexIn(cmd) == -1) + // :set + if (cmd.cmd != "se" && cmd.cmd != "set") return false; showBlackMessage(QString()); - QString arg = reSet.cap(2); - SavedAction *act = theFakeVimSettings()->item(arg); - if (arg.isEmpty()) { - theFakeVimSetting(SettingsDialog)->trigger(QVariant()); - } else if (act && act->value().type() == QVariant::Bool) { - // boolean config to be switched on + SavedAction *act = theFakeVimSettings()->item(cmd.args); + QTC_ASSERT(!cmd.args.isEmpty(), /**/); // Handled by plugin. + if (act && act->value().type() == QVariant::Bool) { + // Boolean config to be switched on. bool oldValue = act->value().toBool(); if (oldValue == false) act->setValue(true); else if (oldValue == true) {} // nothing to do } else if (act) { - // non-boolean to show - showBlackMessage(arg + '=' + act->value().toString()); - } else if (arg.startsWith(QLatin1String("no")) - && (act = theFakeVimSettings()->item(arg.mid(2)))) { - // boolean config to be switched off + // Non-boolean to show. + showBlackMessage(cmd.args + '=' + act->value().toString()); + } else if (cmd.args.startsWith(_("no")) + && (act = theFakeVimSettings()->item(cmd.args.mid(2)))) { + // Boolean config to be switched off. bool oldValue = act->value().toBool(); if (oldValue == true) act->setValue(false); else if (oldValue == false) {} // nothing to do - } else if (arg.contains('=')) { - // non-boolean config to set - int p = arg.indexOf('='); - act = theFakeVimSettings()->item(arg.left(p)); + } else if (cmd.args.contains('=')) { + // Non-boolean config to set. + int p = cmd.args.indexOf('='); + act = theFakeVimSettings()->item(cmd.args.left(p)); if (act) - act->setValue(arg.mid(p + 1)); + act->setValue(cmd.args.mid(p + 1)); } else { - passUnknownSetCommand(arg); + showRedMessage(FakeVimHandler::tr("Unknown option: ") + cmd.args); } updateMiniBuffer(); updateEditor(); return true; } -bool FakeVimHandler::Private::handleExNormalCommand(const QString &cmd) // :normal +bool FakeVimHandler::Private::handleExNormalCommand(const ExCommand &cmd) { - static QRegExp reNormal("^norm(al)?( (.*))?$"); - if (reNormal.indexIn(cmd) == -1) + // :normal + if (cmd.cmd != "norm" && cmd.cmd != "normal") return false; - //qDebug() << "REPLAY: " << reNormal.cap(3); - replay(reNormal.cap(3), 1); + //qDebug() << "REPLAY NORMAL: " << quoteUnprintable(reNormal.cap(3)); + replay(cmd.args, 1); return true; } -bool FakeVimHandler::Private::handleExDeleteCommand(const QString &line) // :d +bool FakeVimHandler::Private::handleExDeleteCommand(const ExCommand &cmd) { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - static QRegExp reDelete("^d( (.*))?$"); - if (reDelete.indexIn(cmd) != -1) + // :delete + if (cmd.cmd != "d" && cmd.cmd != "delete") return false; - selectRange(beginLine, endLine); - QString reg = reDelete.cap(2); - QString text = selectedText(); - removeSelectedText(); + setCurrentRange(cmd.range); + QString reg = cmd.args; + QString text = selectText(cmd.range); + removeText(currentRange()); if (!reg.isEmpty()) { - Register &r = m_registers[reg.at(0).unicode()]; + Register &r = g.registers[reg.at(0).unicode()]; r.contents = text; r.rangemode = RangeLineMode; } return true; } -bool FakeVimHandler::Private::handleExWriteCommand(const QString &line) - // :w, :x, :q, :wq, ... +bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd) { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - static QRegExp reWrite("^[wx]q?a?!?( (.*))?$"); - if (reWrite.indexIn(cmd) == -1) // :w and :x + // :w, :x, :wq, ... + //static QRegExp reWrite("^[wx]q?a?!?( (.*))?$"); + if (cmd.cmd != "w" && cmd.cmd != "x" && cmd.cmd != "wq") return false; - bool noArgs = (beginLine == -1); + int beginLine = lineForPosition(cmd.range.beginPos); + int endLine = lineForPosition(cmd.range.endPos); + const bool noArgs = (beginLine == -1); if (beginLine == -1) beginLine = 0; if (endLine == -1) endLine = linesInDocument(); //qDebug() << "LINES: " << beginLine << endLine; - int indexOfSpace = cmd.indexOf(QChar(' ')); - QString prefix; - if (indexOfSpace < 0) - prefix = cmd; - else - prefix = cmd.left(indexOfSpace); - bool forced = prefix.contains(QChar('!')); - bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x')); - bool quitAll = quit && prefix.contains(QChar('a')); - QString fileName = reWrite.cap(2); + QString prefix = cmd.args; + const bool forced = cmd.hasBang; + //const bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x')); + //const bool quitAll = quit && prefix.contains(QChar('a')); + QString fileName = cmd.args; if (fileName.isEmpty()) fileName = m_currentFileName; QFile file1(fileName); - bool exists = file1.exists(); + const bool exists = file1.exists(); if (exists && !forced && !noArgs) { showRedMessage(FakeVimHandler::tr - ("File '%1' exists (add ! to override)").arg(fileName)); + ("File \"%1\" exists (add ! to override)").arg(fileName)); } else if (file1.open(QIODevice::ReadWrite)) { + // Nobody cared, so act ourselves. file1.close(); QTextCursor tc = m_tc; Range range(firstPositionInLine(beginLine), firstPositionInLine(endLine), RangeLineMode); - QString contents = text(range); + QString contents = selectText(range); m_tc = tc; - //qDebug() << "LINES: " << beginLine << endLine; - bool handled = false; - emit q->writeFileRequested(&handled, fileName, contents); - // nobody cared, so act ourselves - if (!handled) { - //qDebug() << "HANDLING MANUAL SAVE TO " << fileName; - QFile::remove(fileName); - QFile file2(fileName); - if (file2.open(QIODevice::ReadWrite)) { - QTextStream ts(&file2); - ts << contents; - } else { - showRedMessage(FakeVimHandler::tr - ("Cannot open file '%1' for writing").arg(fileName)); - } + QFile::remove(fileName); + QFile file2(fileName); + if (file2.open(QIODevice::ReadWrite)) { + QTextStream ts(&file2); + ts << contents; + } else { + showRedMessage(FakeVimHandler::tr + ("Cannot open file \"%1\" for writing").arg(fileName)); } - // check result by reading back + // Check result by reading back. QFile file3(fileName); file3.open(QIODevice::ReadOnly); QByteArray ba = file3.readAll(); showBlackMessage(FakeVimHandler::tr("\"%1\" %2 %3L, %4C written") .arg(fileName).arg(exists ? " " : " [New] ") .arg(ba.count('\n')).arg(ba.size())); - if (quitAll) - passUnknownExCommand(forced ? "qa!" : "qa"); - else if (quit) - passUnknownExCommand(forced ? "q!" : "q"); + //if (quitAll) + // passUnknownExCommand(forced ? "qa!" : "qa"); + //else if (quit) + // passUnknownExCommand(forced ? "q!" : "q"); } else { showRedMessage(FakeVimHandler::tr - ("Cannot open file '%1' for reading").arg(fileName)); + ("Cannot open file \"%1\" for reading").arg(fileName)); } return true; } -bool FakeVimHandler::Private::handleExReadCommand(const QString &line) // :r +bool FakeVimHandler::Private::handleExReadCommand(const ExCommand &cmd) { - if (!line.startsWith(QLatin1String("r "))) + // :read + if (cmd.cmd != "r" && cmd.cmd != "read") return false; beginEditBlock(); moveToStartOfLine(); setTargetColumn(); moveDown(); - m_currentFileName = line.mid(2); + m_currentFileName = cmd.args; QFile file(m_currentFileName); file.open(QIODevice::ReadOnly); QTextStream ts(&file); @@ -2969,28 +3236,28 @@ bool FakeVimHandler::Private::handleExReadCommand(const QString &line) // :r return true; } -bool FakeVimHandler::Private::handleExBangCommand(const QString &line) // :! +bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :! { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - if (!cmd.startsWith(QLatin1Char('!'))) + if (!cmd.cmd.startsWith(QLatin1Char('!'))) return false; - selectRange(beginLine, endLine); - int targetPosition = firstPositionInLine(beginLine); - QString command = cmd.mid(1).trimmed(); - QString text = selectedText(); + setCurrentRange(cmd.range); + int targetPosition = firstPositionInLine(lineForPosition(cmd.range.beginPos)); + QString command = QString(cmd.cmd.mid(1) + ' ' + cmd.args).trimmed(); + QString text = selectText(cmd.range); QProcess proc; - proc.start(cmd.mid(1)); + proc.start(command); proc.waitForStarted(); +#ifdef Q_OS_WIN + text.replace(_("\n"), _("\r\n")); +#endif proc.write(text.toUtf8()); proc.closeWriteChannel(); proc.waitForFinished(); QString result = QString::fromUtf8(proc.readAllStandardOutput()); beginEditBlock(targetPosition); - removeSelectedText(); - m_tc.insertText(result); + removeText(currentRange()); + insertText(result); setPosition(targetPosition); endEditBlock(); leaveVisualMode(); @@ -3000,26 +3267,29 @@ bool FakeVimHandler::Private::handleExBangCommand(const QString &line) // :! return true; } -bool FakeVimHandler::Private::handleExShiftRightCommand(const QString &line) // :> +bool FakeVimHandler::Private::handleExShiftCommand(const ExCommand &cmd) { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - if (!cmd.startsWith(QLatin1Char('>'))) + if (cmd.cmd != "<" && cmd.cmd != ">") return false; - m_anchor = firstPositionInLine(beginLine); - setPosition(firstPositionInLine(endLine)); - shiftRegionRight(1); + setCurrentRange(cmd.range); + int count = qMin(1, cmd.args.toInt()); + if (cmd.cmd == "<") + shiftRegionLeft(count); + else + shiftRegionRight(count); leaveVisualMode(); - showBlackMessage(FakeVimHandler::tr("%n lines >ed %1 time", 0, - (endLine - beginLine + 1)).arg(1)); + const int beginLine = lineForPosition(cmd.range.beginPos); + const int endLine = lineForPosition(cmd.range.endPos); + showBlackMessage(FakeVimHandler::tr("%n lines %1ed %2 time", 0, + (endLine - beginLine + 1)).arg(cmd.cmd).arg(count)); return true; } -bool FakeVimHandler::Private::handleExRedoCommand(const QString &line) // :redo +bool FakeVimHandler::Private::handleExRedoCommand(const ExCommand &cmd) { - if (line != "red" && line != "redo") + // :redo + if (cmd.cmd != "red" && cmd.cmd != "redo") return false; redo(); @@ -3027,26 +3297,25 @@ bool FakeVimHandler::Private::handleExRedoCommand(const QString &line) // :redo return true; } -bool FakeVimHandler::Private::handleExGotoCommand(const QString &line) // :<nr> +bool FakeVimHandler::Private::handleExGotoCommand(const ExCommand &cmd) { - int beginLine, endLine; - QString cmd = extractCommand(line, &beginLine, &endLine); - - if (!cmd.isEmpty()) + // :<nr> + if (!cmd.cmd.isEmpty()) return false; + const int beginLine = lineForPosition(cmd.range.beginPos); setPosition(firstPositionInLine(beginLine)); showBlackMessage(QString()); return true; } -bool FakeVimHandler::Private::handleExSourceCommand(const QString &line) // :source +bool FakeVimHandler::Private::handleExSourceCommand(const ExCommand &cmd) { - int pos = line.indexOf(' '); - if (line.leftRef(pos) != "so" && line.leftRef(pos) != "source") + // :source + if (cmd.cmd != "so" && cmd.cmd != "source") return false; - QString fileName = line.mid(pos + 1); + QString fileName = cmd.args; QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { showRedMessage(FakeVimHandler::tr("Can't open file %1").arg(fileName)); @@ -3079,119 +3348,137 @@ bool FakeVimHandler::Private::handleExSourceCommand(const QString &line) // :sou void FakeVimHandler::Private::handleExCommand(const QString &line0) { QString line = line0; // Make sure we have a copy to prevent aliasing. + // FIXME: that seems to be different for %w and %s + if (line.startsWith(QLatin1Char('%'))) + line = "1,$" + line.mid(1); + + int beginLine = readLineCode(line); + int endLine = -1; + if (line.startsWith(',')) { + line = line.mid(1); + endLine = readLineCode(line); + } + if (beginLine != -1 && endLine == -1) + endLine = beginLine; + const int beginPos = firstPositionInLine(beginLine); + const int endPos = lastPositionInLine(endLine); + ExCommand cmd; + const QString arg0 = line.section(' ', 0, 0); + cmd.cmd = arg0; + cmd.args = line.mid(arg0.size() + 1).trimmed(); + cmd.range = Range(beginPos, endPos, RangeLineMode); + cmd.hasBang = arg0.endsWith('!'); + if (cmd.hasBang) + cmd.cmd.chop(1); + //qDebug() << "CMD: " << cmd; + enterCommandMode(); showBlackMessage(QString()); - if (handleExCommandHelper(line)) - return; - int beginLine, endLine; - passUnknownExCommand(extractCommand(line, &beginLine, &endLine)); + if (!handleExCommandHelper(cmd)) + showRedMessage(tr("Not an editor command: %1").arg(cmd.cmd)); } -bool FakeVimHandler::Private::handleExCommandHelper(const QString &line) +bool FakeVimHandler::Private::handleExCommandHelper(const ExCommand &cmd) { - return handleExGotoCommand(line) - || handleExBangCommand(line) - || handleExHistoryCommand(line) - || handleExMapCommand(line) - || handleExNormalCommand(line) - || handleExReadCommand(line) - || handleExRedoCommand(line) - || handleExSetCommand(line) - || handleExShiftRightCommand(line) - || handleExSourceCommand(line) - || handleExSubstituteCommand(line) - || handleExWriteCommand(line); + return handleExPluginCommand(cmd) + || handleExGotoCommand(cmd) + || handleExBangCommand(cmd) + || handleExHistoryCommand(cmd) + || handleExRegisterCommand(cmd) + || handleExDeleteCommand(cmd) + || handleExMapCommand(cmd) + || handleExNormalCommand(cmd) + || handleExReadCommand(cmd) + || handleExRedoCommand(cmd) + || handleExSetCommand(cmd) + || handleExShiftCommand(cmd) + || handleExSourceCommand(cmd) + || handleExSubstituteCommand(cmd) + || handleExWriteCommand(cmd); } -void FakeVimHandler::Private::passUnknownExCommand(const QString &cmd) +bool FakeVimHandler::Private::handleExPluginCommand(const ExCommand &cmd) { EDITOR(setTextCursor(m_tc)); - emit q->handleExCommandRequested(cmd); + bool handled = false; + emit q->handleExCommandRequested(&handled, cmd); if (m_plaintextedit || m_textedit) m_tc = EDITOR(textCursor()); -} - -void FakeVimHandler::Private::passUnknownSetCommand(const QString &arg) -{ - bool handled = false; - emit q->handleSetCommandRequested(&handled, arg); - if (!handled) { - showRedMessage(FakeVimHandler::tr("E512: Unknown option: ") + arg); - } + //qDebug() << "HANDLER REQUEST: " << cmd.cmd << handled; + return handled; } static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *flags) { // FIXME: Rough mapping of a common case - if (needle->startsWith(QLatin1String("\\<")) && needle->endsWith(QLatin1String("\\>"))) + if (needle->startsWith(_("\\<")) && needle->endsWith(_("\\>"))) (*flags) |= QTextDocument::FindWholeWords; - needle->remove(QLatin1String("\\<")); // start of word - needle->remove(QLatin1String("\\>")); // end of word + needle->remove(_("\\<")); // start of word + needle->remove(_("\\>")); // end of word //qDebug() << "NEEDLE " << needle0 << needle; } -void FakeVimHandler::Private::search(const QString &needle0, bool forward, - bool incSearch) +void FakeVimHandler::Private::search(const SearchData &sd) { - showBlackMessage((forward ? '/' : '?') + needle0); - CursorPosition origPosition = cursorPosition(); + if (sd.needle.isEmpty()) + return; + + const bool incSearch = hasConfig(ConfigIncSearch); QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively; - if (!forward) + if (!sd.forward) flags |= QTextDocument::FindBackward; - QString needle = needle0; + QString needle = sd.needle; vimPatternToQtPattern(&needle, &flags); - if (forward) - m_tc.movePosition(Right, MoveAnchor, 1); + const int oldLine = cursorLineInDocument() - cursorLineOnScreen(); - int oldLine = cursorLineInDocument() - cursorLineOnScreen(); + int startPos = position(); + if (sd.mustMove) + sd.forward ? ++startPos : --startPos; - EDITOR(setTextCursor(m_tc)); - if (EDITOR(find(needle, flags))) { - m_tc = EDITOR(textCursor()); - m_tc.setPosition(m_tc.anchor()); - // making this unconditional feels better, but is not "vim like" - if (oldLine != cursorLineInDocument() - cursorLineOnScreen()) - scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2); - if (!incSearch) - highlightMatches(needle); - } else { - m_tc.setPosition(forward ? 0 : lastPositionInDocument()); - EDITOR(setTextCursor(m_tc)); - if (EDITOR(find(needle, flags))) { - m_tc = EDITOR(textCursor()); - m_tc.setPosition(m_tc.anchor()); - if (oldLine != cursorLineInDocument() - cursorLineOnScreen()) - scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2); + m_searchCursor = QTextCursor(); + QTextCursor tc = m_tc.document()->find(needle, startPos, flags); + if (tc.isNull()) { + int startPos = sd.forward ? 0 : lastPositionInDocument(); + tc = m_tc.document()->find(needle, startPos, flags); + if (tc.isNull()) { if (!incSearch) { - if (forward) - showRedMessage(FakeVimHandler::tr("search hit BOTTOM, continuing at TOP")); - else - showRedMessage(FakeVimHandler::tr("search hit TOP, continuing at BOTTOM")); - highlightMatches(needle); - } - } else { - if (!incSearch) highlightMatches(QString()); - setCursorPosition(origPosition); - if (!incSearch) showRedMessage(FakeVimHandler::tr("Pattern not found: ") + needle); + } + updateSelection(); + return; + } + if (!incSearch) { + QString msg = sd.forward + ? FakeVimHandler::tr("search hit BOTTOM, continuing at TOP") + : FakeVimHandler::tr("search hit TOP, continuing at BOTTOM"); + showRedMessage(msg); } } - if (incSearch) { - QTextEdit::ExtraSelection sel; - sel.cursor = m_tc; - sel.format = m_tc.blockCharFormat(); - sel.format.setForeground(Qt::white); - sel.format.setBackground(Qt::black); - sel.cursor.setPosition(m_tc.position(), MoveAnchor); - sel.cursor.setPosition(m_tc.position() + needle.size(), KeepAnchor); - QList<QTextEdit::ExtraSelection> selections = m_searchSelections; - selections.append(sel); - emit q->selectionChanged(selections); + + // Set Cursor. + tc.setPosition(qMin(tc.position(), tc.anchor()), MoveAnchor); + tc.clearSelection(); + m_tc = tc; + EDITOR(setTextCursor(m_tc)); + + // Making this unconditional feels better, but is not "vim like". + if (oldLine != cursorLineInDocument() - cursorLineOnScreen()) + scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2); + + if (incSearch && sd.highlightCursor) { + m_searchCursor = m_tc; + m_searchCursor.setPosition(m_tc.position(), MoveAnchor); + m_searchCursor.setPosition(m_tc.position() + needle.size(), KeepAnchor); } setTargetColumn(); + + if (sd.highlightMatches) + highlightMatches(needle); + updateSelection(); + recordJump(); } void FakeVimHandler::Private::highlightMatches(const QString &needle0) @@ -3202,7 +3489,6 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle0) return; m_oldNeedle = needle0; m_searchSelections.clear(); - if (!needle0.isEmpty()) { QTextCursor tc = m_tc; tc.movePosition(StartOfDocument, MoveAnchor); @@ -3210,18 +3496,16 @@ void FakeVimHandler::Private::highlightMatches(const QString &needle0) QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively; QString needle = needle0; vimPatternToQtPattern(&needle, &flags); - - - EDITOR(setTextCursor(tc)); - while (EDITOR(find(needle, flags))) { - tc = EDITOR(textCursor()); + while (1) { + tc = tc.document()->find(needle, tc.position(), flags); + if (tc.isNull()) + break; QTextEdit::ExtraSelection sel; sel.cursor = tc; sel.format = tc.blockCharFormat(); sel.format.setBackground(QColor(177, 177, 0)); m_searchSelections.append(sel); tc.movePosition(Right, MoveAnchor); - EDITOR(setTextCursor(tc)); } } updateSelection(); @@ -3247,8 +3531,7 @@ void FakeVimHandler::Private::indentSelectedText(QChar typedChar) int beginLine = qMin(lineForPosition(position()), lineForPosition(anchor())); int endLine = qMax(lineForPosition(position()), lineForPosition(anchor())); - Range range(anchor(), position()); - range.rangemode = m_rangemode; + Range range(anchor(), position(), m_rangemode); indentText(range, typedChar); setPosition(firstPositionInLine(beginLine)); @@ -3266,8 +3549,10 @@ int FakeVimHandler::Private::indentText(const Range &range, QChar typedChar) int amount = 0; // lineForPosition has returned 1-based line numbers - emit q->indentRegion(&amount, beginLine-1, endLine-1, typedChar); - + emit q->indentRegion(&amount, beginLine - 1, endLine - 1, typedChar); + fixMarks(firstPositionInLine(beginLine), amount); + if (beginLine != endLine) + showBlackMessage("MARKS ARE OFF NOW"); return amount; } @@ -3313,7 +3598,7 @@ void FakeVimHandler::Private::shiftRegionLeft(int repeat) int targetPos = anchor(); if (beginLine > endLine) { qSwap(beginLine, endLine); - targetPos = position(); + targetPos = position(); } const int shift = config(ConfigShiftWidth).toInt() * repeat; const int tab = config(ConfigTabStop).toInt(); @@ -3635,12 +3920,7 @@ int FakeVimHandler::Private::lastPositionInDocument() const return block.position() + block.length() - 1; } -QString FakeVimHandler::Private::lastSearchString() const -{ - return m_searchHistory.empty() ? QString() : m_searchHistory.back(); -} - -QString FakeVimHandler::Private::text(const Range &range) const +QString FakeVimHandler::Private::selectText(const Range &range) const { if (range.rangemode == RangeCharMode) { QTextCursor tc = m_tc; @@ -3688,34 +3968,35 @@ QString FakeVimHandler::Private::text(const Range &range) const return contents; } -void FakeVimHandler::Private::yankSelectedText() -{ - Range range(anchor(), position()); - range.rangemode = m_rangemode; - yankText(range, m_register); -} - void FakeVimHandler::Private::yankText(const Range &range, int toregister) { - Register ® = m_registers[toregister]; - reg.contents = text(range); + Register ® = g.registers[toregister]; + reg.contents = selectText(range); reg.rangemode = range.rangemode; //qDebug() << "YANKED: " << reg.contents; } void FakeVimHandler::Private::transformText(const Range &range, - Transformation transformFunc) + Transformation transformFunc, const QVariant &extra) { QTextCursor tc = m_tc; switch (range.rangemode) { case RangeCharMode: { + // This can span multiple lines. + beginEditBlock(); tc.setPosition(range.beginPos, MoveAnchor); tc.setPosition(range.endPos, KeepAnchor); - (this->*transformFunc)(range.beginPos, &tc); + TransformationData td(tc.selectedText(), extra); + (this->*transformFunc)(&td); + tc.removeSelectedText(); + fixMarks(range.beginPos, td.to.size() - td.from.size()); + tc.insertText(td.to); + endEditBlock(); return; } case RangeLineMode: case RangeLineModeExclusive: { + beginEditBlock(range.beginPos); tc.setPosition(range.beginPos, MoveAnchor); tc.movePosition(StartOfLine, MoveAnchor); tc.setPosition(range.endPos, KeepAnchor); @@ -3737,7 +4018,12 @@ void FakeVimHandler::Private::transformText(const Range &range, tc.movePosition(Right, KeepAnchor, 1); } } - (this->*transformFunc)(range.beginPos, &tc); + TransformationData td(tc.selectedText(), extra); + (this->*transformFunc)(&td); + tc.removeSelectedText(); + fixMarks(range.beginPos, td.to.size() - td.from.size()); + tc.insertText(td.to); + endEditBlock(); return; } case RangeBlockAndTailMode: @@ -3757,7 +4043,11 @@ void FakeVimHandler::Private::transformText(const Range &range, int eCol = qMin(endColumn + 1, block.length() - 1); tc.setPosition(block.position() + bCol, MoveAnchor); tc.setPosition(block.position() + eCol, KeepAnchor); - (this->*transformFunc)(block.position() + bCol, &tc); + TransformationData td(tc.selectedText(), extra); + (this->*transformFunc)(&td); + tc.removeSelectedText(); + fixMarks(block.position() + bCol, td.to.size() - td.from.size()); + tc.insertText(td.to); block = block.previous(); } endEditBlock(); @@ -3765,11 +4055,12 @@ void FakeVimHandler::Private::transformText(const Range &range, } } -void FakeVimHandler::Private::removeSelectedText() +void FakeVimHandler::Private::insertText(const Register ®) { - Range range(anchor(), position()); - range.rangemode = m_rangemode; - removeText(range); + QTC_ASSERT(reg.rangemode == RangeCharMode, + qDebug() << "WRONG INSERT MODE: " << reg.rangemode; return); + fixMarks(position(), reg.contents.length()); + m_tc.insertText(reg.contents); } void FakeVimHandler::Private::removeText(const Range &range) @@ -3777,100 +4068,69 @@ void FakeVimHandler::Private::removeText(const Range &range) transformText(range, &FakeVimHandler::Private::removeTransform); } -void FakeVimHandler::Private::removeTransform(int updateMarksAfter, QTextCursor *tc) +void FakeVimHandler::Private::removeTransform(TransformationData *td) { - fixMarks(updateMarksAfter, tc->selectionStart() - tc->selectionEnd()); - tc->removeSelectedText(); + Q_UNUSED(td); } -void FakeVimHandler::Private::downCaseSelectedText() +void FakeVimHandler::Private::downCase(const Range &range) { - Range range(anchor(), position()); - range.rangemode = m_rangemode; transformText(range, &FakeVimHandler::Private::downCaseTransform); } -void FakeVimHandler::Private::downCaseTransform(int updateMarksAfter, QTextCursor *tc) +void FakeVimHandler::Private::downCaseTransform(TransformationData *td) { - Q_UNUSED(updateMarksAfter); - QString str = tc->selectedText(); - tc->removeSelectedText(); - for (int i = str.size(); --i >= 0; ) { - QChar c = str.at(i); - str[i] = c.toLower(); - } - tc->insertText(str); + td->to = td->from.toLower(); } -void FakeVimHandler::Private::upCaseSelectedText() +void FakeVimHandler::Private::upCase(const Range &range) { - Range range(anchor(), position()); - range.rangemode = m_rangemode; transformText(range, &FakeVimHandler::Private::upCaseTransform); } -void FakeVimHandler::Private::upCaseTransform(int updateMarksAfter, QTextCursor *tc) +void FakeVimHandler::Private::upCaseTransform(TransformationData *td) { - Q_UNUSED(updateMarksAfter); - QString str = tc->selectedText(); - tc->removeSelectedText(); - for (int i = str.size(); --i >= 0; ) { - QChar c = str.at(i); - str[i] = c.toUpper(); - } - tc->insertText(str); + td->to = td->from.toUpper(); } -void FakeVimHandler::Private::invertCaseSelectedText() +void FakeVimHandler::Private::invertCase(const Range &range) { - Range range(anchor(), position()); - range.rangemode = m_rangemode; transformText(range, &FakeVimHandler::Private::invertCaseTransform); } -void FakeVimHandler::Private::invertCaseTransform(int updateMarksAfter, QTextCursor *tc) +void FakeVimHandler::Private::invertCaseTransform(TransformationData *td) { - Q_UNUSED(updateMarksAfter); - QString str = tc->selectedText(); - tc->removeSelectedText(); - for (int i = str.size(); --i >= 0; ) { - QChar c = str.at(i); - str[i] = c.isUpper() ? c.toLower() : c.toUpper(); - } - tc->insertText(str); + foreach (QChar c, td->from) + td->to += c.isUpper() ? c.toLower() : c.toUpper(); } -void FakeVimHandler::Private::replaceSelectedText() +void FakeVimHandler::Private::replaceText(const Range &range, const QString &str) { - Range range(anchor(), position()); - range.rangemode = m_rangemode; - transformText(range, &FakeVimHandler::Private::replaceTransform); + Transformation tr = &FakeVimHandler::Private::replaceByStringTransform; + transformText(range, tr, str); } -void FakeVimHandler::Private::replaceTransform(int updateMarksAfter, QTextCursor *tc) +void FakeVimHandler::Private::replaceByStringTransform(TransformationData *td) { - Q_UNUSED(updateMarksAfter); - QString str = tc->selectedText(); - tc->removeSelectedText(); - for (int i = str.size(); --i >= 0; ) { - QChar c = str.at(i); - str[i] = (c.toAscii() == '\n' || c.toAscii() == '\0') ? QChar('\n') : m_replacingCharacter; - } - tc->insertText(str); + td->to = td->extraData.toString(); +} + +void FakeVimHandler::Private::replaceByCharTransform(TransformationData *td) +{ + td->to = QString(td->from.size(), td->extraData.toChar()); } void FakeVimHandler::Private::pasteText(bool afterCursor) { - const QString text = m_registers[m_register].contents; + const QString text = g.registers[m_register].contents; const QStringList lines = text.split(QChar('\n')); - switch (m_registers[m_register].rangemode) { + switch (g.registers[m_register].rangemode) { case RangeCharMode: { m_targetColumn = 0; for (int i = count(); --i >= 0; ) { if (afterCursor && rightDist() > 0) moveRight(); - fixMarks(position(), text.length()); - m_tc.insertText(text); + insertText(text); if (!afterCursor && atEndOfLine()) moveLeft(); moveLeft(); @@ -3884,8 +4144,7 @@ void FakeVimHandler::Private::pasteText(bool afterCursor) for (int i = count(); --i >= 0; ) { if (afterCursor) moveDown(); - fixMarks(position(), text.length()); - m_tc.insertText(text); + insertText(text); moveUp(lines.size() - 1); } moveToFirstNonBlankOnLine(); @@ -3910,8 +4169,8 @@ void FakeVimHandler::Private::pasteText(bool afterCursor) } else { tc.movePosition(Right, MoveAnchor, col - 1 + afterCursor); } - qDebug() << "INSERT " << line << " AT " << tc.position() - << "COL: " << col; + //qDebug() << "INSERT " << line << " AT " << tc.position() + // << "COL: " << col; fixMarks(position(), line.length()); tc.insertText(line); tc.movePosition(StartOfLine, MoveAnchor); @@ -3931,16 +4190,38 @@ void FakeVimHandler::Private::pasteText(bool afterCursor) } //FIXME: This needs to called after undo/insert -void FakeVimHandler::Private::fixMarks(int positionAction, int positionChange) -{ - QHashIterator<int, int> i(m_marks); - while (i.hasNext()) { - i.next(); - if (i.value() >= positionAction) { - if (i.value() + positionChange > 0) - m_marks[i.key()] = i.value() + positionChange; - else - m_marks.remove(i.key()); +// The position 'from' is the cursor position after the change. If 'delta' +// is positive there was a string of size 'delta' inserted after 'from' +// and consequently all marks beyond 'from + delta' need to be incremented +// by 'delta'. If text was removed, 'delta' is negative. All marks between +// 'from' and 'from - delta' need to be removed, everything behing +// 'from - delta' adjusted by 'delta'. +void FakeVimHandler::Private::fixMarks(int from, int delta) +{ + //qDebug() << "ADJUSTING MARKS FROM " << from << " BY " << delta; + if (delta == 0) + return; + QHashIterator<int, int> it(m_marks); + while (it.hasNext()) { + it.next(); + int pos = it.value(); + if (delta > 0) { + // Inserted text. + if (pos >= from) { + //qDebug() << "MODIFIED: " << it.key() << pos; + setMark(it.key(), pos + delta); + } + } else { + // Removed text. + if (pos < from) { + // Nothing to do. + } else if (pos < from - delta) { + //qDebug() << "GONE: " << it.key(); + m_marks.remove(it.key()); + } else { + //qDebug() << "MODIFIED: " << it.key() << pos; + setMark(it.key(), pos + delta); + } } } } @@ -3950,13 +4231,14 @@ QString FakeVimHandler::Private::lineContents(int line) const return m_tc.document()->findBlockByNumber(line - 1).text(); } -void FakeVimHandler::Private::setLineContents(int line, const QString &contents) const +void FakeVimHandler::Private::setLineContents(int line, const QString &contents) { QTextBlock block = m_tc.document()->findBlockByNumber(line - 1); QTextCursor tc = m_tc; tc.setPosition(block.position()); tc.setPosition(block.position() + block.length() - 1, KeepAnchor); tc.removeSelectedText(); + fixMarks(block.position(), block.length() - contents.size()); tc.insertText(contents); } @@ -3983,8 +4265,8 @@ void FakeVimHandler::Private::enterVisualMode(VisualMode visualMode) setAnchor(); m_positionPastEnd = m_anchorPastEnd = false; m_visualMode = visualMode; - m_marks['<'] = m_tc.position(); - m_marks['>'] = m_tc.position(); + setMark('<', m_tc.position()); + setMark('>', m_tc.position()); updateMiniBuffer(); updateSelection(); } @@ -4011,11 +4293,16 @@ QWidget *FakeVimHandler::Private::editor() const void FakeVimHandler::Private::undo() { //qDebug() << " CURSOR POS: " << m_undoCursorPosition; - int current = m_tc.document()->availableUndoSteps(); - //endEditBlock(); + QTextDocument *doc = m_tc.document(); + // FIXME: That's only an approximaxtion. The real solution might + // be to store marks and old userData with QTextBlock setUserData + // and retrieve them afterward. + const int current = doc->availableUndoSteps(); + const int oldCount = doc->characterCount(); EDITOR(undo()); - //beginEditBlock(); - int rev = m_tc.document()->availableUndoSteps(); + const int delta = doc->characterCount() - oldCount; + fixMarks(position(), delta); + const int rev = doc->availableUndoSteps(); if (current == rev) showBlackMessage(FakeVimHandler::tr("Already at oldest change")); else @@ -4023,17 +4310,20 @@ void FakeVimHandler::Private::undo() if (m_undoCursorPosition.contains(rev)) m_tc.setPosition(m_undoCursorPosition[rev]); + setTargetColumn(); if (atEndOfLine()) moveLeft(); } void FakeVimHandler::Private::redo() { - int current = m_tc.document()->availableUndoSteps(); - //endEditBlock(); + QTextDocument *doc = m_tc.document(); + const int current = m_tc.document()->availableUndoSteps(); + const int oldCount = doc->characterCount(); EDITOR(redo()); - //beginEditBlock(); - int rev = m_tc.document()->availableUndoSteps(); + const int delta = doc->characterCount() - oldCount; + fixMarks(position(), delta); + const int rev = doc->availableUndoSteps(); if (rev == current) showBlackMessage(FakeVimHandler::tr("Already at newest change")); else @@ -4041,32 +4331,63 @@ void FakeVimHandler::Private::redo() if (m_undoCursorPosition.contains(rev)) m_tc.setPosition(m_undoCursorPosition[rev]); + setTargetColumn(); +} + +void FakeVimHandler::Private::updateCursor() +{ + if (m_mode == ExMode || m_subsubmode == SearchSubSubMode) { + EDITOR(setCursorWidth(0)); + EDITOR(setOverwriteMode(false)); + } else if (m_mode == InsertMode) { + EDITOR(setCursorWidth(m_cursorWidth)); + EDITOR(setOverwriteMode(false)); + } else { + EDITOR(setCursorWidth(m_cursorWidth)); + EDITOR(setOverwriteMode(true)); + } +} + +void FakeVimHandler::Private::enterReplaceMode() +{ + m_mode = ReplaceMode; + m_submode = NoSubMode; + m_subsubmode = NoSubSubMode; + m_commandPrefix.clear(); + m_lastInsertion.clear(); + m_lastDeletion.clear(); + updateCursor(); } void FakeVimHandler::Private::enterInsertMode() { - EDITOR(setCursorWidth(m_cursorWidth)); - EDITOR(setOverwriteMode(false)); - //leaveVisualMode(); m_mode = InsertMode; + m_submode = NoSubMode; + m_subsubmode = NoSubSubMode; + m_commandPrefix.clear(); m_lastInsertion.clear(); - m_beginEditBlock = true; + m_lastDeletion.clear(); + updateCursor(); } void FakeVimHandler::Private::enterCommandMode() { - EDITOR(setCursorWidth(m_cursorWidth)); - EDITOR(setOverwriteMode(true)); if (atEndOfLine()) moveLeft(); m_mode = CommandMode; + m_submode = NoSubMode; + m_subsubmode = NoSubSubMode; + m_commandPrefix.clear(); + updateCursor(); } void FakeVimHandler::Private::enterExMode() { - EDITOR(setCursorWidth(0)); - EDITOR(setOverwriteMode(false)); m_mode = ExMode; + m_submode = NoSubMode; + m_subsubmode = NoSubSubMode; + m_commandPrefix = ":"; + updateCursor(); } void FakeVimHandler::Private::recordJump() @@ -4076,13 +4397,6 @@ void FakeVimHandler::Private::recordJump() UNDO_DEBUG("jumps: " << m_jumpListUndo); } -void FakeVimHandler::Private::recordNewUndo() -{ - //endEditBlock(); - UNDO_DEBUG("---- BREAK ----"); - //beginEditBlock(); -} - Column FakeVimHandler::Private::indentation(const QString &line) const { int ts = config(ConfigTabStop).toInt(); @@ -4128,7 +4442,7 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown) ++pos; text.truncate(pos); // FIXME: handle 'smartindent' and 'cindent' - m_tc.insertText(text); + insertText(text); m_justAutoIndented = text.size(); } } @@ -4139,6 +4453,7 @@ bool FakeVimHandler::Private::removeAutomaticIndentation() return false; m_tc.movePosition(StartOfLine, KeepAnchor); m_tc.removeSelectedText(); + fixMarks(m_tc.position(), -m_justAutoIndented); m_lastInsertion.chop(m_justAutoIndented); m_justAutoIndented = 0; return true; @@ -4152,18 +4467,17 @@ void FakeVimHandler::Private::handleStartOfLine() void FakeVimHandler::Private::replay(const QString &command, int n) { - //qDebug() << "REPLAY: " << command; - m_inReplay = true; + //qDebug() << "REPLAY: " << quoteUnprintable(command); + g.inReplay = true; for (int i = n; --i >= 0; ) { foreach (QChar c, command) { //qDebug() << " REPLAY: " << QString(c); handleKey(Input(c)); } } - m_inReplay = false; + g.inReplay = false; } - void FakeVimHandler::Private::selectWordTextObject(bool inner) { Q_UNUSED(inner); // FIXME @@ -4172,7 +4486,7 @@ void FakeVimHandler::Private::selectWordTextObject(bool inner) setAnchor(); // FIXME: Rework the 'anchor' concept. if (isVisualMode()) - m_marks['<'] = m_tc.position(); + setMark('<', m_tc.position()); moveToWordBoundary(false, true, true); m_movetype = MoveInclusive; } @@ -4185,7 +4499,7 @@ void FakeVimHandler::Private::selectWORDTextObject(bool inner) setAnchor(); // FIXME: Rework the 'anchor' concept. if (isVisualMode()) - m_marks['<'] = m_tc.position(); + setMark('<', m_tc.position()); moveToWordBoundary(true, true, true); m_movetype = MoveInclusive; } @@ -4213,6 +4527,18 @@ void FakeVimHandler::Private::selectQuotedStringTextObject(bool inner, int type) Q_UNUSED(type); } +int FakeVimHandler::Private::mark(int code) const +{ + // FIXME: distinguish local and global marks. + //qDebug() << "MARK: " << code << m_marks.value(code, -1) << m_marks; + return m_marks.value(code, -1); +} + +void FakeVimHandler::Private::setMark(int code, int position) +{ + // FIXME: distinguish local and global marks. + m_marks[code] = position; +} /////////////////////////////////////////////////////////////////////// // @@ -4240,6 +4566,30 @@ bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev) { bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool(); + // Catch mouse events on the viewport. + QWidget *viewport = 0; + if (d->m_plaintextedit) + viewport = d->m_plaintextedit->viewport(); + else if (d->m_textedit) + viewport = d->m_textedit->viewport(); + if (ob == viewport) { + if (active && ev->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mev = static_cast<QMouseEvent *>(ev); + if (mev->button() == Qt::LeftButton) { + d->importSelection(); + //return true; + } + } + if (active && ev->type() == QEvent::MouseButtonPress) { + QMouseEvent *mev = static_cast<QMouseEvent *>(ev); + if (mev->button() == Qt::LeftButton) { + d->m_visualMode = NoVisualMode; + d->updateSelection(); + } + } + return QObject::eventFilter(ob, ev); + } + if (active && ev->type() == QEvent::Shortcut) { d->passShortcuts(false); return false; @@ -4314,6 +4664,11 @@ void FakeVimHandler::setCurrentFileName(const QString &fileName) d->m_currentFileName = fileName; } +QString FakeVimHandler::currentFileName() const +{ + return d->m_currentFileName; +} + void FakeVimHandler::showBlackMessage(const QString &msg) { d->showBlackMessage(msg); diff --git a/src/plugins/fakevim/fakevimhandler.h b/src/plugins/fakevim/fakevimhandler.h index 7543d8cf862a4610d4edb73bd1e0b985e7f2781f..4e877d31c8ec3e42602207be0e03cf0cc1b0f418 100644 --- a/src/plugins/fakevim/fakevimhandler.h +++ b/src/plugins/fakevim/fakevimhandler.h @@ -38,6 +38,38 @@ namespace FakeVim { namespace Internal { +enum RangeMode +{ + RangeCharMode, // v + RangeLineMode, // V + RangeLineModeExclusive, + RangeBlockMode, // Ctrl-v + RangeBlockAndTailMode, // Ctrl-v for D and X +}; + +struct Range +{ + Range(); + Range(int b, int e, RangeMode m = RangeCharMode); + QString toString() const; + + int beginPos; + int endPos; + RangeMode rangemode; +}; + +struct ExCommand +{ + ExCommand() : hasBang(false) {} + ExCommand(const QString &cmd, const QString &args = QString(), + const Range &range = Range()); + + QString cmd; + bool hasBang; + QString args; + Range range; +}; + class FakeVimHandler : public QObject { Q_OBJECT @@ -53,6 +85,8 @@ public: public slots: void setCurrentFileName(const QString &fileName); + QString currentFileName() const; + void showBlackMessage(const QString &msg); void showRedMessage(const QString &msg); @@ -76,8 +110,6 @@ signals: void statusDataChanged(const QString &msg); void extraInformationChanged(const QString &msg); void selectionChanged(const QList<QTextEdit::ExtraSelection> &selection); - void writeFileRequested(bool *handled, - const QString &fileName, const QString &contents); void writeAllRequested(QString *error); void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor); void checkForElectricCharacter(bool *result, QChar c); @@ -86,8 +118,7 @@ signals: void windowCommandRequested(int key); void findRequested(bool reverse); void findNextRequested(bool reverse); - void handleExCommandRequested(const QString &cmd); - void handleSetCommandRequested(bool *handled, const QString &cmd); + void handleExCommandRequested(bool *handled, const ExCommand &cmd); public: class Private; @@ -101,4 +132,7 @@ private: } // namespace Internal } // namespace FakeVim +Q_DECLARE_METATYPE(FakeVim::Internal::ExCommand); + + #endif // FAKEVIM_HANDLER_H diff --git a/src/plugins/fakevim/fakevimoptions.ui b/src/plugins/fakevim/fakevimoptions.ui index 6a9f52b5abf7ad993a0d4e662b37ba5ac729bd3c..450a8629065d6f947f8b0e40c76c8e9d5a9eb98e 100644 --- a/src/plugins/fakevim/fakevimoptions.ui +++ b/src/plugins/fakevim/fakevimoptions.ui @@ -66,6 +66,13 @@ </property> </widget> </item> + <item row="2" column="2"> + <widget class="QCheckBox" name="checkBoxShowMarks"> + <property name="text"> + <string>Show position of text marks</string> + </property> + </widget> + </item> <item row="3" column="0" colspan="2"> <widget class="QCheckBox" name="checkBoxSmartTab"> <property name="text"> diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index a698cb01aa2ccb221c0363f8f2297890ad7cac7c..5c66cec0786f3ffa9532f78fc58d50129cc133df 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -73,9 +73,9 @@ #include <indenter.h> #include <QtCore/QDebug> +#include <QtCore/QFile> #include <QtCore/QtPlugin> #include <QtCore/QObject> -#include <QtCore/QPoint> #include <QtCore/QSettings> #include <QtCore/QTextStream> @@ -120,6 +120,8 @@ const char * const CMD_FILE_PREV = "FakeVim.SwitchFilePrev"; namespace FakeVim { namespace Internal { +typedef QMap<QString, QRegExp> CommandMap; + class FakeVimOptionPage : public Core::IOptionsPage { Q_OBJECT @@ -168,6 +170,8 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent) m_ui.checkBoxHlSearch); m_group.insert(theFakeVimSetting(ConfigShiftWidth), m_ui.spinBoxShiftWidth); + m_group.insert(theFakeVimSetting(ConfigShowMarks), + m_ui.checkBoxShowMarks); m_group.insert(theFakeVimSetting(ConfigSmartTab), m_ui.checkBoxSmartTab); @@ -199,8 +203,8 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent) QTextStream(&m_searchKeywords) << ' ' << m_ui.checkBoxAutoIndent->text() << ' ' << m_ui.checkBoxExpandTab->text() + << ' ' << m_ui.checkBoxShowMarks->text() << ' ' << m_ui.checkBoxSmartIndent->text() - << ' ' << m_ui.checkBoxExpandTab->text() << ' ' << m_ui.checkBoxHlSearch->text() << ' ' << m_ui.checkBoxIncSearch->text() << ' ' << m_ui.checkBoxSmartTab->text() @@ -278,16 +282,12 @@ Q_DECLARE_METATYPE(CommandItem*); namespace FakeVim { namespace Internal { -static QMap<QString, QRegExp> s_exCommandMap; -static QMap<QString, QRegExp> s_defaultExCommandMap; - - class FakeVimExCommandsPage : public Core::CommandMappings { Q_OBJECT public: - FakeVimExCommandsPage() {} + FakeVimExCommandsPage(FakeVimPluginPrivate *q) : m_q(q) {} // IOptionsPage QString id() const { return QLatin1String(Constants::SETTINGS_EX_CMDS_ID); } @@ -298,6 +298,8 @@ public: QWidget *createPage(QWidget *parent); void initialize(); + CommandMap &exCommandMap(); + CommandMap &defaultExCommandMap(); public slots: void commandChanged(QTreeWidgetItem *current); @@ -309,6 +311,7 @@ public slots: private: void setRegex(const QString ®ex); QList<CommandItem *> m_citems; + FakeVimPluginPrivate *m_q; }; QWidget *FakeVimExCommandsPage::createPage(QWidget *parent) @@ -341,15 +344,16 @@ void FakeVimExCommandsPage::initialize() QTreeWidgetItem *item = new QTreeWidgetItem; ci->m_cmd = c; ci->m_item = item; - m_citems << ci; + m_citems.append(ci); const QString name = uidm->stringForUniqueIdentifier(c->id()); const int pos = name.indexOf(QLatin1Char('.')); const QString section = name.left(pos); - const QString subId = name.mid(pos+1); + const QString subId = name.mid(pos + 1); if (!sections.contains(section)) { - QTreeWidgetItem *categoryItem = new QTreeWidgetItem(commandList(), QStringList() << section); + QTreeWidgetItem *categoryItem = + new QTreeWidgetItem(commandList(), QStringList() << section); QFont f = categoryItem->font(0); f.setBold(true); categoryItem->setFont(0, f); @@ -361,14 +365,16 @@ void FakeVimExCommandsPage::initialize() item->setText(0, subId); if (c->action()) { - QString text = c->hasAttribute(Command::CA_UpdateText) && !c->defaultText().isNull() ? c->defaultText() : c->action()->text(); + QString text = c->hasAttribute(Command::CA_UpdateText) + && !c->defaultText().isNull() + ? c->defaultText() : c->action()->text(); text.remove(QRegExp("&(?!&)")); item->setText(1, text); } else { item->setText(1, c->shortcut()->whatsThis()); } - if (s_exCommandMap.contains(name)) { - ci->m_regex = s_exCommandMap[name].pattern(); + if (exCommandMap().contains(name)) { + ci->m_regex = exCommandMap()[name].pattern(); } else { ci->m_regex.clear(); } @@ -376,7 +382,7 @@ void FakeVimExCommandsPage::initialize() item->setText(2, ci->m_regex); item->setData(0, Qt::UserRole, qVariantFromValue(ci)); - if (ci->m_regex != s_defaultExCommandMap[name].pattern()) + if (ci->m_regex != defaultExCommandMap()[name].pattern()) setModified(item, true); } @@ -407,10 +413,10 @@ void FakeVimExCommandsPage::targetIdentifierChanged() if (current->data(0, Qt::UserRole).isValid()) { citem->m_regex = targetEdit()->text(); current->setText(2, citem->m_regex); - s_exCommandMap[name] = QRegExp(citem->m_regex); + exCommandMap()[name] = QRegExp(citem->m_regex); } - if (citem->m_regex != s_defaultExCommandMap[name].pattern()) + if (citem->m_regex != defaultExCommandMap()[name].pattern()) setModified(current, true); else setModified(current, false); @@ -429,8 +435,8 @@ void FakeVimExCommandsPage::resetTargetIdentifier() if (current && current->data(0, Qt::UserRole).isValid()) { CommandItem *citem = qVariantValue<CommandItem *>(current->data(0, Qt::UserRole)); const QString &name = uidm->stringForUniqueIdentifier(citem->m_cmd->id()); - if (s_defaultExCommandMap.contains(name)) - setRegex(s_defaultExCommandMap[name].pattern()); + if (defaultExCommandMap().contains(name)) + setRegex(defaultExCommandMap()[name].pattern()); else setRegex(QString()); } @@ -446,8 +452,8 @@ void FakeVimExCommandsPage::defaultAction() UniqueIDManager *uidm = UniqueIDManager::instance(); foreach (CommandItem *item, m_citems) { const QString &name = uidm->stringForUniqueIdentifier(item->m_cmd->id()); - if (s_defaultExCommandMap.contains(name)) { - item->m_regex = s_defaultExCommandMap[name].pattern(); + if (defaultExCommandMap().contains(name)) { + item->m_regex = defaultExCommandMap()[name].pattern(); } else { item->m_regex.clear(); } @@ -479,6 +485,7 @@ public: FakeVimPluginPrivate(FakeVimPlugin *); ~FakeVimPluginPrivate(); friend class FakeVimPlugin; + friend class FakeVimExCommandsPage; bool initialize(); void aboutToShutdown(); @@ -499,12 +506,10 @@ private slots: void showCommandBuffer(const QString &contents); void showExtraInformation(const QString &msg); void changeSelection(const QList<QTextEdit::ExtraSelection> &selections); - void writeFile(bool *handled, const QString &fileName, const QString &contents); void moveToMatchingParenthesis(bool *moved, bool *forward, QTextCursor *cursor); void checkForElectricCharacter(bool *result, QChar c); void indentRegion(int *amount, int beginLine, int endLine, QChar typedChar); - void handleExCommand(const QString &cmd); - void handleSetCommand(bool *handled, QString cmd); + void handleExCommand(bool *handled, const ExCommand &cmd); void handleDelayedQuitAll(bool forced); void handleDelayedQuit(bool forced, Core::IEditor *editor); @@ -523,11 +528,16 @@ private: FakeVimExCommandsPage *m_fakeVimExCommandsPage; QHash<Core::IEditor *, FakeVimHandler *> m_editorToHandler; - void triggerAction(const QString& code); - void setActionChecked(const QString& code, bool check); + void triggerAction(const QString &code); + void setActionChecked(const QString &code, bool check); void readSettings(QSettings *settings); void writeSettings(QSettings *settings); + + CommandMap &exCommandMap() { return m_exCommandMap; } + CommandMap &defaultExCommandMap() { return m_exCommandMap; } + CommandMap m_exCommandMap; + CommandMap m_defaultExCommandMap; }; } // namespace Internal @@ -538,23 +548,22 @@ FakeVimPluginPrivate::FakeVimPluginPrivate(FakeVimPlugin *plugin) q = plugin; m_fakeVimOptionsPage = 0; m_fakeVimExCommandsPage = 0; - - s_defaultExCommandMap[Constants::CMD_FILE_NEXT] = + defaultExCommandMap()[Constants::CMD_FILE_NEXT] = QRegExp("^n(ext)?!?( (.*))?$"); - s_defaultExCommandMap[Constants::CMD_FILE_PREV] = + defaultExCommandMap()[Constants::CMD_FILE_PREV] = QRegExp("^(N(ext)?|prev(ious)?)!?( (.*))?$"); - s_defaultExCommandMap[CppTools::Constants::SWITCH_HEADER_SOURCE] = + defaultExCommandMap()[CppTools::Constants::SWITCH_HEADER_SOURCE] = QRegExp("^A$"); - s_defaultExCommandMap[ProjectExplorer::Constants::BUILD] = - QRegExp("^make$"); - s_defaultExCommandMap["Coreplugin.OutputPane.previtem"] = + defaultExCommandMap()["Coreplugin.OutputPane.previtem"] = QRegExp("^(cN(ext)?|cp(revious)?)!?( (.*))?$"); - s_defaultExCommandMap["Coreplugin.OutputPane.nextitem"] = + defaultExCommandMap()["Coreplugin.OutputPane.nextitem"] = QRegExp("^cn(ext)?!?( (.*))?$"); - s_defaultExCommandMap[CppEditor::Constants::JUMP_TO_DEFINITION] = + defaultExCommandMap()[CppEditor::Constants::JUMP_TO_DEFINITION] = QRegExp("^tag?$"); - s_defaultExCommandMap[Core::Constants::GO_BACK] = + defaultExCommandMap()[Core::Constants::GO_BACK] = QRegExp("^pop?$"); + defaultExCommandMap()[QLatin1String("QtCreator.Locate")] = + QRegExp("^e$"); } FakeVimPluginPrivate::~FakeVimPluginPrivate() @@ -587,7 +596,7 @@ bool FakeVimPluginPrivate::initialize() q->addObject(m_fakeVimOptionsPage); theFakeVimSettings()->readSettings(Core::ICore::instance()->settings()); - m_fakeVimExCommandsPage = new FakeVimExCommandsPage; + m_fakeVimExCommandsPage = new FakeVimExCommandsPage(this); q->addObject(m_fakeVimExCommandsPage); readSettings(Core::ICore::instance()->settings()); @@ -607,8 +616,6 @@ bool FakeVimPluginPrivate::initialize() connect(editorManager, SIGNAL(editorOpened(Core::IEditor*)), this, SLOT(editorOpened(Core::IEditor*))); - connect(theFakeVimSetting(SettingsDialog), SIGNAL(triggered()), - this, SLOT(showSettingsDialog())); connect(theFakeVimSetting(ConfigUseFakeVim), SIGNAL(valueChanged(QVariant)), this, SLOT(setUseFakeVim(QVariant))); connect(theFakeVimSetting(ConfigReadVimRc), SIGNAL(valueChanged(QVariant)), @@ -624,7 +631,7 @@ bool FakeVimPluginPrivate::initialize() cmd->setAttribute(Command::CA_Hide); connect(switchFilePrevAction, SIGNAL(triggered()), this, SLOT(switchFilePrev())); - // Delayed operatiosn + // Delayed operations. connect(this, SIGNAL(delayedQuitRequested(bool,Core::IEditor*)), this, SLOT(handleDelayedQuit(bool,Core::IEditor*)), Qt::QueuedConnection); connect(this, SIGNAL(delayedQuitAllRequested(bool)), @@ -644,14 +651,14 @@ void FakeVimPluginPrivate::writeSettings(QSettings *settings) settings->beginWriteArray(QLatin1String(exCommandMapGroup)); int count = 0; - typedef QMap<QString, QRegExp>::const_iterator Iterator; - const Iterator end = s_exCommandMap.constEnd(); - for (Iterator it = s_exCommandMap.constBegin(); it != end; ++it) { + typedef CommandMap::const_iterator Iterator; + const Iterator end = exCommandMap().constEnd(); + for (Iterator it = exCommandMap().constBegin(); it != end; ++it) { const QString &id = it.key(); const QRegExp &re = it.value(); - if ((s_defaultExCommandMap.contains(id) && s_defaultExCommandMap[id] != re) - || (!s_defaultExCommandMap.contains(id) && !re.pattern().isEmpty())) { + if ((defaultExCommandMap().contains(id) && defaultExCommandMap()[id] != re) + || (!defaultExCommandMap().contains(id) && !re.pattern().isEmpty())) { settings->setArrayIndex(count); settings->setValue(QLatin1String(idKey), id); settings->setValue(QLatin1String(reKey), re.pattern()); @@ -664,14 +671,14 @@ void FakeVimPluginPrivate::writeSettings(QSettings *settings) void FakeVimPluginPrivate::readSettings(QSettings *settings) { - s_exCommandMap = s_defaultExCommandMap; + exCommandMap() = defaultExCommandMap(); int size = settings->beginReadArray(QLatin1String(exCommandMapGroup)); for (int i = 0; i < size; ++i) { settings->setArrayIndex(i); const QString id = settings->value(QLatin1String(idKey)).toString(); const QString re = settings->value(QLatin1String(reKey)).toString(); - s_exCommandMap[id] = QRegExp(re); + exCommandMap()[id] = QRegExp(re); } settings->endArray(); } @@ -702,18 +709,18 @@ void FakeVimPluginPrivate::showSettingsDialog() QLatin1String(Constants::SETTINGS_ID)); } -void FakeVimPluginPrivate::triggerAction(const QString& code) +void FakeVimPluginPrivate::triggerAction(const QString &code) { Core::ActionManager *am = Core::ICore::instance()->actionManager(); QTC_ASSERT(am, return); Core::Command *cmd = am->command(code); - QTC_ASSERT(cmd, return); + QTC_ASSERT(cmd, qDebug() << "UNKNOW CODE: " << code; return); QAction *action = cmd->action(); QTC_ASSERT(action, return); action->trigger(); } -void FakeVimPluginPrivate::setActionChecked(const QString& code, bool check) +void FakeVimPluginPrivate::setActionChecked(const QString &code, bool check) { Core::ActionManager *am = Core::ICore::instance()->actionManager(); QTC_ASSERT(am, return); @@ -826,8 +833,6 @@ void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor) this, SLOT(showExtraInformation(QString))); connect(handler, SIGNAL(commandBufferChanged(QString)), this, SLOT(showCommandBuffer(QString))); - connect(handler, SIGNAL(writeFileRequested(bool*,QString,QString)), - this, SLOT(writeFile(bool*,QString,QString))); connect(handler, SIGNAL(selectionChanged(QList<QTextEdit::ExtraSelection>)), this, SLOT(changeSelection(QList<QTextEdit::ExtraSelection>))); connect(handler, SIGNAL(moveToMatchingParenthesis(bool*,bool*,QTextCursor*)), @@ -845,10 +850,8 @@ void FakeVimPluginPrivate::editorOpened(Core::IEditor *editor) connect(handler, SIGNAL(findNextRequested(bool)), this, SLOT(findNext(bool))); - connect(handler, SIGNAL(handleExCommandRequested(QString)), - this, SLOT(handleExCommand(QString))); - connect(handler, SIGNAL(handleSetCommandRequested(bool *,QString)), - this, SLOT(handleSetCommand(bool *,QString))); + connect(handler, SIGNAL(handleExCommandRequested(bool*,ExCommand)), + this, SLOT(handleExCommand(bool*,ExCommand))); handler->setCurrentFileName(editor->file()->fileName()); handler->installEventFilter(); @@ -909,33 +912,12 @@ void FakeVimPluginPrivate::checkForElectricCharacter(bool *result, QChar c) *result = bt->isElectricCharacter(c); } -void FakeVimPluginPrivate::writeFile(bool *handled, - const QString &fileName, const QString &contents) -{ - Q_UNUSED(contents) - - FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender()); - if (!handler) - return; - - Core::IEditor *editor = m_editorToHandler.key(handler); - if (editor && editor->file()->fileName() == fileName) { - // Handle that as a special case for nicer interaction with core - Core::IFile *file = editor->file(); - Core::ICore::instance()->fileManager()->blockFileChange(file); - file->save(fileName); - Core::ICore::instance()->fileManager()->unblockFileChange(file); - *handled = true; - } -} - -void FakeVimPluginPrivate::handleExCommand(const QString &cmd) +void FakeVimPluginPrivate::handleExCommand(bool *handled, const ExCommand &cmd) { - static QRegExp reWriteAll("^wa(ll)?!?$"); - static QRegExp reQuit("^q!?$"); - static QRegExp reQuitAll("^qa!?$"); - using namespace Core; + //qDebug() << "PLUGIN HANDLE: " << cmd.cmd; + + *handled = false; FakeVimHandler *handler = qobject_cast<FakeVimHandler *>(sender()); if (!handler) @@ -944,7 +926,27 @@ void FakeVimPluginPrivate::handleExCommand(const QString &cmd) EditorManager *editorManager = EditorManager::instance(); QTC_ASSERT(editorManager, return); - if (reWriteAll.indexIn(cmd) != -1) { + *handled = true; + if (cmd.cmd == "w" || cmd.cmd == "write") { + Core::IEditor *editor = m_editorToHandler.key(handler); + const QString fileName = handler->currentFileName(); + if (editor && editor->file()->fileName() == fileName) { + // Handle that as a special case for nicer interaction with core + Core::IFile *file = editor->file(); + Core::ICore::instance()->fileManager()->blockFileChange(file); + file->save(fileName); + Core::ICore::instance()->fileManager()->unblockFileChange(file); + // Check result by reading back. + QFile file3(fileName); + file3.open(QIODevice::ReadOnly); + QByteArray ba = file3.readAll(); + handler->showBlackMessage(FakeVimHandler::tr("\"%1\" %2 %3L, %4C written") + .arg(fileName).arg(" ") + .arg(ba.count('\n')).arg(ba.size())); + } else { + handler->showRedMessage(tr("File not saved")); + } + } else if (cmd.cmd == "wa" || cmd.cmd == "wall") { // :wa FileManager *fm = ICore::instance()->fileManager(); QList<IFile *> toSave = fm->modifiedFiles(); @@ -953,43 +955,47 @@ void FakeVimPluginPrivate::handleExCommand(const QString &cmd) handler->showBlackMessage(tr("Saving succeeded")); else handler->showRedMessage(tr("%n files not saved", 0, failed.size())); - } else if (reQuit.indexIn(cmd) != -1) { - // :q - bool forced = cmd.contains(QChar('!')); - emit delayedQuitRequested(forced, m_editorToHandler.key(handler)); - } else if (reQuitAll.indexIn(cmd) != -1) { + } else if (cmd.cmd == "q" || cmd.cmd == "quit") { + // :q[uit] + emit delayedQuitRequested(cmd.hasBang, m_editorToHandler.key(handler)); + } else if (cmd.cmd == "qa" || cmd.cmd == "qall") { // :qa - bool forced = cmd.contains(QChar('!')); - emit delayedQuitAllRequested(forced); + emit delayedQuitAllRequested(cmd.hasBang); + } else if (cmd.cmd == "sp" || cmd.cmd == "split") { + // :sp[lit] + triggerAction(Core::Constants::SPLIT); + } else if (cmd.cmd == "vs" || cmd.cmd == "vsplit") { + // :vs[plit] + triggerAction(Core::Constants::SPLIT_SIDE_BY_SIDE); + } else if (cmd.cmd == "mak" || cmd.cmd == "make") { + // :mak[e][!] [arguments] + triggerAction(ProjectExplorer::Constants::BUILD); + } else if (cmd.cmd == "se" || cmd.cmd == "set") { + if (cmd.args.isEmpty()) { + // :set + showSettingsDialog(); + } else if (cmd.args == "ic" || cmd.args == "ignorecase") { + // :set noic + setActionChecked(Find::Constants::CASE_SENSITIVE, false); + *handled = false; // Let the handler see it as well. + } else if (cmd.args == "noic" || cmd.args == "noignorecase") { + // :set noic + setActionChecked(Find::Constants::CASE_SENSITIVE, true); + *handled = false; // Let the handler see it as well. + } } else { - typedef QMap<QString, QRegExp>::const_iterator Iterator; - const Iterator end = s_exCommandMap.constEnd(); - for (Iterator it = s_exCommandMap.constBegin(); it != end; ++it) { + // Check whether one of the configure commands matches. + typedef CommandMap::const_iterator Iterator; + const Iterator end = exCommandMap().constEnd(); + for (Iterator it = exCommandMap().constBegin(); it != end; ++it) { const QString &id = it.key(); const QRegExp &re = it.value(); - - if (!re.pattern().isEmpty() && re.indexIn(cmd) != -1) { + if (!re.pattern().isEmpty() && re.indexIn(cmd.cmd) != -1) { triggerAction(id); return; } } - - handler->showRedMessage(tr("Not an editor command: %1").arg(cmd)); - } -} - -void FakeVimPluginPrivate::handleSetCommand(bool *handled, QString cmd) -{ - *handled = false; - bool value = true; - if (cmd.startsWith("no")) { - value = false; - cmd = cmd.mid(2); - } - - if (cmd == "ic" || cmd == "ignorecase") { - setActionChecked(Find::Constants::CASE_SENSITIVE, value); - *handled = true; + *handled = false; } } @@ -1064,8 +1070,7 @@ void FakeVimPluginPrivate::indentRegion(int *amount, int beginLine, int endLine, const QTextDocument *doc = bt->document(); const TextEditor::TextBlockIterator docStart(doc->begin()); QTextBlock cur = doc->findBlockByNumber(beginLine); - for(int i = beginLine; i<= endLine; ++i) - { + for (int i = beginLine; i <= endLine; ++i) { if (typedChar == 0 && cur.text().simplified().isEmpty()) { // clear empty lines *amount = 0; @@ -1130,6 +1135,17 @@ void FakeVimPluginPrivate::switchFilePrev() switchFile(true); } +CommandMap &FakeVimExCommandsPage::exCommandMap() +{ + return m_q->exCommandMap(); +} + +CommandMap &FakeVimExCommandsPage::defaultExCommandMap() +{ + return m_q->defaultExCommandMap(); +} + + /////////////////////////////////////////////////////////////////////// // // FakeVimPlugin