1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** Licensees holding valid Qt Commercial licenses may use this file in
12 ** accordance with the Qt Commercial License Agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and Nokia.
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
28 **************************************************************************/
30 #include "fakevimhandler.h"
35 // 1 Please do not add any direct dependencies to other Qt Creator code here.
36 // Instead emit signals and let the FakeVimPlugin channel the information to
37 // Qt Creator. The idea is to keep this file here in a "clean" state that
38 // allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
40 // 2 There are a few auto tests located in ../../../tests/auto/fakevim.
41 // Commands that are covered there are marked as "// tested" below.
43 // 3 Some conventions:
45 // Use 1 based line numbers and 0 based column numbers. Even though
46 // the 1 based line are not nice it matches vim's and QTextEdit's 'line'
49 // Do not pass QTextCursor etc around unless really needed. Convert
50 // early to line/column.
52 // There is always a "current" cursor (m_tc). A current "region of interest"
53 // spans between m_anchor (== anchor()) and m_tc.position() (== position())
54 // The value of m_tc.anchor() is not used.
57 #include <utils/qtcassert.h>
59 #include <QtCore/QDebug>
60 #include <QtCore/QFile>
61 #include <QtCore/QObject>
62 #include <QtCore/QPointer>
63 #include <QtCore/QProcess>
64 #include <QtCore/QRegExp>
65 #include <QtCore/QTextStream>
66 #include <QtCore/QtAlgorithms>
67 #include <QtCore/QStack>
69 #include <QtGui/QApplication>
70 #include <QtGui/QKeyEvent>
71 #include <QtGui/QLineEdit>
72 #include <QtGui/QPlainTextEdit>
73 #include <QtGui/QScrollBar>
74 #include <QtGui/QTextBlock>
75 #include <QtGui/QTextCursor>
76 #include <QtGui/QTextDocumentFragment>
77 #include <QtGui/QTextEdit>
81 // FIXME: Restrict this as soon the availableUndoSteps has been merged to Qt
82 #if QT_VERSION < 0x040600
83 #define availableUndoSteps revision
88 # define KEY_DEBUG(s) qDebug() << s
93 //#define DEBUG_UNDO 1
95 # define UNDO_DEBUG(s) qDebug() << << m_tc.document()->availableUndoSteps() << s
97 # define UNDO_DEBUG(s)
100 using namespace Utils;
105 ///////////////////////////////////////////////////////////////////////
109 ///////////////////////////////////////////////////////////////////////
111 #define StartOfLine QTextCursor::StartOfLine
112 #define EndOfLine QTextCursor::EndOfLine
113 #define MoveAnchor QTextCursor::MoveAnchor
114 #define KeepAnchor QTextCursor::KeepAnchor
115 #define Up QTextCursor::Up
116 #define Down QTextCursor::Down
117 #define Right QTextCursor::Right
118 #define Left QTextCursor::Left
119 #define EndOfDocument QTextCursor::End
120 #define StartOfDocument QTextCursor::Start
122 #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
124 const int ParagraphSeparator = 0x00002029;
141 ChangeSubMode, // used for c
142 DeleteSubMode, // used for d
143 FilterSubMode, // used for !
144 IndentSubMode, // used for =
145 RegisterSubMode, // used for "
146 ReplaceSubMode, // used for R and r
147 ShiftLeftSubMode, // used for <
148 ShiftRightSubMode, // used for >
149 WindowSubMode, // used for Ctrl-w
150 YankSubMode, // used for y
151 ZSubMode, // used for z
152 CapitalZSubMode // used for Z
157 // typically used for things that require one more data item
158 // and are 'nested' behind a mode
160 FtSubSubMode, // used for f, F, t, T
161 MarkSubSubMode, // used for m
162 BackTickSubSubMode, // used for `
163 TickSubSubMode, // used for '
195 struct CursorPosition
198 CursorPosition() : position(-1), scrollLine(-1) {}
199 CursorPosition(int pos, int line) : position(pos), scrollLine(line) {}
200 int position; // Position in document
201 int scrollLine; // First visible line
206 Register() : rangemode(RangeCharMode) {}
207 Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
215 : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
218 Range(int b, int e, RangeMode m = RangeCharMode)
219 : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
227 QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
229 foreach (QTextEdit::ExtraSelection sel, sels)
230 ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
234 QString quoteUnprintable(const QString &ba)
237 for (int i = 0, n = ba.size(); i != n; ++i) {
242 res += QString("\\x%1").arg(c.unicode(), 2, 16);
247 inline QString msgE20MarkNotSet(const QString &text)
249 return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text);
252 class FakeVimHandler::Private
255 Private(FakeVimHandler *parent, QWidget *widget);
257 EventResult handleEvent(QKeyEvent *ev);
258 bool wantsOverride(QKeyEvent *ev);
259 void handleCommand(const QString &cmd); // sets m_tc + handleExCommand
260 void handleExCommand(const QString &cmd);
261 void fixMarks(int positionAction, int positionChange); //Updates marks positions by the difference in positionChange
263 void installEventFilter();
265 void restoreWidget();
267 friend class FakeVimHandler;
268 static int shift(int key) { return key + 32; }
269 static int control(int key) { return key + 256; }
272 EventResult handleKey(int key, int unmodified, const QString &text);
273 EventResult handleInsertMode(int key, int unmodified, const QString &text);
274 EventResult handleCommandMode(int key, int unmodified, const QString &text);
275 EventResult handleRegisterMode(int key, int unmodified, const QString &text);
276 EventResult handleMiniBufferModes(int key, int unmodified, const QString &text);
277 void finishMovement(const QString &text = QString());
278 void search(const QString &needle, bool forward);
279 void highlightMatches(const QString &needle);
281 int mvCount() const { return m_mvcount.isEmpty() ? 1 : m_mvcount.toInt(); }
282 int opCount() const { return m_opcount.isEmpty() ? 1 : m_opcount.toInt(); }
283 int count() const { return mvCount() * opCount(); }
284 int leftDist() const { return m_tc.position() - m_tc.block().position(); }
285 int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
286 bool atEndOfLine() const
287 { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
289 int lastPositionInDocument() const; // last valid pos in doc
290 int firstPositionInLine(int line) const; // 1 based line, 0 based pos
291 int lastPositionInLine(int line) const; // 1 based line, 0 based pos
292 int lineForPosition(int pos) const; // 1 based line, 0 based pos
294 // all zero-based counting
295 int cursorLineOnScreen() const;
296 int linesOnScreen() const;
297 int columnsOnScreen() const;
298 int cursorLineInDocument() const;
299 int cursorColumnInDocument() const;
300 int linesInDocument() const;
301 int firstVisibleLineInDocument() const;
302 void scrollToLineInDocument(int line);
303 void scrollUp(int count);
304 void scrollDown(int count) { scrollUp(-count); }
306 CursorPosition cursorPosition() const
307 { return CursorPosition(position(), firstVisibleLineInDocument()); }
308 void setCursorPosition(const CursorPosition &p)
309 { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }
311 // helper functions for indenting
312 bool isElectricCharacter(QChar c) const
313 { return c == '{' || c == '}' || c == '#'; }
314 void indentRegion(QChar lastTyped = QChar());
315 void shiftRegionLeft(int repeat = 1);
316 void shiftRegionRight(int repeat = 1);
318 void moveToFirstNonBlankOnLine();
319 void moveToTargetColumn();
320 void setTargetColumn() {
321 m_targetColumn = leftDist();
322 //qDebug() << "TARGET: " << m_targetColumn;
324 void moveToNextWord(bool simple);
325 void moveToMatchingParanthesis();
326 void moveToWordBoundary(bool simple, bool forward);
328 // to reduce line noise
329 void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
330 void moveToStartOfLine();
331 void moveToEndOfLine();
332 void moveBehindEndOfLine();
333 void moveUp(int n = 1) { moveDown(-n); }
334 void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
335 void moveRight(int n = 1) { m_tc.movePosition(Right, MoveAnchor, n); }
336 void moveLeft(int n = 1) { m_tc.movePosition(Left, MoveAnchor, n); }
337 void setAnchor() { m_anchor = m_tc.position(); }
338 void setAnchor(int position) { m_anchor = position; }
339 void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
341 void handleFfTt(int key);
343 // helper function for handleExCommand. return 1 based line index.
344 int readLineCode(QString &cmd);
345 void selectRange(int beginLine, int endLine);
347 void enterInsertMode();
348 void enterCommandMode();
350 void showRedMessage(const QString &msg);
351 void showBlackMessage(const QString &msg);
352 void notImplementedYet();
353 void updateMiniBuffer();
354 void updateSelection();
355 QWidget *editor() const;
356 QChar characterAtCursor() const
357 { return m_tc.document()->characterAt(m_tc.position()); }
358 void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
359 void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
360 void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
362 // this asks the layer above (e.g. the fake vim plugin or the
363 // stand-alone test application to handle the command)
364 void passUnknownExCommand(const QString &cmd);
367 QTextEdit *m_textedit;
368 QPlainTextEdit *m_plaintextedit;
369 bool m_wasReadOnly; // saves read-only state of document
373 bool m_passing; // let the core see the next event
375 SubSubMode m_subsubmode;
379 QTextCursor m_oldTc; // copy from last event to check for external changes
381 static QHash<int, Register> m_registers;
386 RangeMode m_rangemode;
390 bool isSearchMode() const
391 { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
392 int m_gflag; // whether current command started with 'g'
394 QString m_commandBuffer;
395 QString m_currentFileName;
396 QString m_currentMessage;
398 bool m_lastSearchForward;
399 QString m_lastInsertion;
401 int anchor() const { return m_anchor; }
402 int position() const { return m_tc.position(); }
404 void removeSelectedText();
405 void removeText(const Range &range);
407 QString selectedText() const { return text(Range(position(), anchor())); }
408 QString text(const Range &range) const;
410 void yankSelectedText();
411 void yankText(const Range &range, int toregister = '"');
413 void pasteText(bool afterCursor);
418 QMap<int, int> m_undoCursorPosition; // revision -> position
420 // extra data for '.'
421 void replay(const QString &text, int count);
422 void setDotCommand(const QString &cmd) { m_dotCommand = cmd; }
423 void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); }
424 QString m_dotCommand;
425 bool m_inReplay; // true if we are executing a '.'
427 // extra data for ';'
428 QString m_semicolonCount;
429 int m_semicolonType; // 'f', 'F', 't', 'T'
433 QString lastSearchString() const;
434 static QStringList m_searchHistory;
435 int m_searchHistoryIndex;
438 static QStringList m_commandHistory;
439 int m_commandHistoryIndex;
442 void enterVisualMode(VisualMode visualMode);
443 void leaveVisualMode();
444 VisualMode m_visualMode;
447 QHash<int, int> m_marks;
450 // vi style configuration
451 QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
452 bool hasConfig(int code) const { return config(code).toBool(); }
453 bool hasConfig(int code, const char *value) const // FIXME
454 { return config(code).toString().contains(value); }
456 // for restoring cursor position
457 int m_savedYankPosition;
463 void insertAutomaticIndentation(bool goingDown);
464 bool removeAutomaticIndentation(); // true if something removed
465 // number of autoindented characters
466 int m_justAutoIndented;
467 void handleStartOfLine();
470 void recordNewUndo();
471 QVector<CursorPosition> m_jumpListUndo;
472 QVector<CursorPosition> m_jumpListRedo;
474 QList<QTextEdit::ExtraSelection> m_searchSelections;
477 QStringList FakeVimHandler::Private::m_searchHistory;
478 QStringList FakeVimHandler::Private::m_commandHistory;
479 QHash<int, Register> FakeVimHandler::Private::m_registers;
481 FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
484 m_textedit = qobject_cast<QTextEdit *>(widget);
485 m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
489 void FakeVimHandler::Private::init()
491 m_mode = CommandMode;
492 m_submode = NoSubMode;
493 m_subsubmode = NoSubSubMode;
496 m_lastSearchForward = true;
499 m_visualMode = NoVisualMode;
501 m_movetype = MoveInclusive;
503 m_savedYankPosition = 0;
504 m_cursorWidth = EDITOR(cursorWidth());
506 m_justAutoIndented = 0;
507 m_rangemode = RangeCharMode;
510 bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
512 const int key = ev->key();
513 const int mods = ev->modifiers();
514 KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing);
516 if (key == Key_Escape) {
517 // Not sure this feels good. People often hit Esc several times
518 if (m_visualMode == NoVisualMode && m_mode == CommandMode)
523 // We are interested in overriding most Ctrl key combinations
524 if (mods == Qt::ControlModifier && key >= Key_A && key <= Key_Z && key != Key_K) {
525 // Ctrl-K is special as it is the Core's default notion of Locator
527 KEY_DEBUG(" PASSING CTRL KEY");
528 // We get called twice on the same key
532 KEY_DEBUG(" NOT PASSING CTRL KEY");
533 //updateMiniBuffer();
537 // Let other shortcuts trigger
541 EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
544 const int um = key; // keep unmodified key around
545 const int mods = ev->modifiers();
547 if (key == Key_Shift || key == Key_Alt || key == Key_Control
548 || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
550 KEY_DEBUG("PLAIN MODIFIER");
551 return EventUnhandled;
555 KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
556 //if (key == ',') { // use ',,' to leave, too.
557 // qDebug() << "FINISHED...";
558 // return EventHandled;
562 KEY_DEBUG(" PASS TO CORE");
563 return EventPassedToCore;
566 // Fake "End of line"
567 m_tc = EDITOR(textCursor());
569 if (m_tc.position() != m_oldTc.position())
572 m_tc.setVisualNavigation(true);
577 if ((mods & Qt::ControlModifier) != 0) {
579 key += 32; // make it lower case
580 } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
584 //if (m_mode == InsertMode)
585 // joinPreviousEditBlock();
588 EventResult result = handleKey(key, um, ev->text());
591 // We fake vi-style end-of-line behaviour
592 m_fakeEnd = (atEndOfLine() && m_mode == CommandMode
593 && m_visualMode != VisualBlockMode);
599 EDITOR(setTextCursor(m_tc));
603 void FakeVimHandler::Private::installEventFilter()
605 EDITOR(installEventFilter(q));
608 void FakeVimHandler::Private::setupWidget()
611 //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
613 m_textedit->setLineWrapMode(QTextEdit::NoWrap);
614 } else if (m_plaintextedit) {
615 m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
617 m_wasReadOnly = EDITOR(isReadOnly());
618 //EDITOR(setReadOnly(true));
620 QTextCursor tc = EDITOR(textCursor());
621 if (tc.hasSelection()) {
622 int pos = tc.position();
623 int anc = tc.anchor();
627 m_visualMode = VisualCharMode;
629 EDITOR(setTextCursor(tc));
630 m_tc = tc; // needed in updateSelection
634 //showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
638 void FakeVimHandler::Private::restoreWidget()
640 //showBlackMessage(QString());
641 //updateMiniBuffer();
642 //EDITOR(removeEventFilter(q));
643 EDITOR(setReadOnly(m_wasReadOnly));
644 EDITOR(setCursorWidth(m_cursorWidth));
645 EDITOR(setOverwriteMode(false));
647 if (m_visualMode == VisualLineMode) {
648 m_tc = EDITOR(textCursor());
649 int beginLine = lineForPosition(m_marks['<']);
650 int endLine = lineForPosition(m_marks['>']);
651 m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor);
652 m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor);
653 EDITOR(setTextCursor(m_tc));
654 } else if (m_visualMode == VisualCharMode) {
655 m_tc = EDITOR(textCursor());
656 m_tc.setPosition(m_marks['<'], MoveAnchor);
657 m_tc.setPosition(m_marks['>'], KeepAnchor);
658 EDITOR(setTextCursor(m_tc));
661 m_visualMode = NoVisualMode;
665 EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
668 m_undoCursorPosition[m_tc.document()->availableUndoSteps()] = m_tc.position();
669 //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
670 if (m_mode == InsertMode)
671 return handleInsertMode(key, unmodified, text);
672 if (m_mode == CommandMode)
673 return handleCommandMode(key, unmodified, text);
674 if (m_mode == ExMode || m_mode == SearchForwardMode
675 || m_mode == SearchBackwardMode)
676 return handleMiniBufferModes(key, unmodified, text);
677 return EventUnhandled;
680 void FakeVimHandler::Private::moveDown(int n)
683 // does not work for "hidden" documents like in the autotests
684 m_tc.movePosition(Down, MoveAnchor, n);
686 const int col = m_tc.position() - m_tc.block().position();
687 const int lastLine = m_tc.document()->lastBlock().blockNumber();
688 const int targetLine = qMax(0, qMin(lastLine, m_tc.block().blockNumber() + n));
689 const QTextBlock &block = m_tc.document()->findBlockByNumber(targetLine);
690 const int pos = block.position();
691 setPosition(pos + qMin(block.length() - 1, col));
692 moveToTargetColumn();
696 void FakeVimHandler::Private::moveToEndOfLine()
699 // does not work for "hidden" documents like in the autotests
700 m_tc.movePosition(EndOfLine, MoveAnchor);
702 const QTextBlock &block = m_tc.block();
703 setPosition(block.position() + block.length() - 1);
707 void FakeVimHandler::Private::moveBehindEndOfLine()
709 const QTextBlock &block = m_tc.block();
710 int pos = qMin(block.position() + block.length(), lastPositionInDocument());
714 void FakeVimHandler::Private::moveToStartOfLine()
717 // does not work for "hidden" documents like in the autotests
718 m_tc.movePosition(StartOfLine, MoveAnchor);
720 const QTextBlock &block = m_tc.block();
721 setPosition(block.position());
725 void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
727 //qDebug() << "ANCHOR: " << position() << anchor();
728 if (m_submode == FilterSubMode) {
729 int beginLine = lineForPosition(anchor());
730 int endLine = lineForPosition(position());
731 setPosition(qMin(anchor(), position()));
733 m_currentMessage.clear();
734 m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
735 m_commandHistory.append(QString());
736 m_commandHistoryIndex = m_commandHistory.size() - 1;
741 if (m_visualMode != NoVisualMode)
742 m_marks['>'] = m_tc.position();
744 if (m_submode == ChangeSubMode) {
745 if (m_movetype == MoveInclusive)
746 moveRight(); // correction
747 if (anchor() >= position())
749 if (!dotCommand.isEmpty())
750 setDotCommand("c" + dotCommand);
751 //QString text = removeSelectedText();
752 //qDebug() << "CHANGING TO INSERT MODE" << text;
753 //m_registers[m_register] = text;
755 removeSelectedText();
757 m_submode = NoSubMode;
758 } else if (m_submode == DeleteSubMode) {
759 if (m_rangemode == RangeCharMode) {
760 if (m_movetype == MoveInclusive)
761 moveRight(); // correction
762 if (anchor() >= position())
765 if (!dotCommand.isEmpty())
766 setDotCommand("d" + dotCommand);
768 removeSelectedText();
769 m_submode = NoSubMode;
774 } else if (m_submode == YankSubMode) {
776 m_submode = NoSubMode;
777 if (m_register != '"') {
778 setPosition(m_marks[m_register]);
781 setPosition(m_savedYankPosition);
783 } else if (m_submode == ReplaceSubMode) {
784 m_submode = NoSubMode;
785 } else if (m_submode == IndentSubMode) {
788 m_submode = NoSubMode;
790 } else if (m_submode == ShiftRightSubMode) {
793 m_submode = NoSubMode;
795 } else if (m_submode == ShiftLeftSubMode) {
798 m_submode = NoSubMode;
802 m_movetype = MoveInclusive;
807 m_tc.clearSelection();
808 m_rangemode = RangeCharMode;
814 void FakeVimHandler::Private::updateSelection()
816 QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
817 if (m_visualMode != NoVisualMode) {
818 QTextEdit::ExtraSelection sel;
820 sel.format = m_tc.blockCharFormat();
822 sel.format.setFontWeight(QFont::Bold);
823 sel.format.setFontUnderline(true);
825 sel.format.setForeground(Qt::white);
826 sel.format.setBackground(Qt::black);
828 int cursorPos = m_tc.position();
829 int anchorPos = m_marks['<'];
830 //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
831 if (m_visualMode == VisualCharMode) {
832 sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
833 sel.cursor.setPosition(qMax(cursorPos, anchorPos) + 1, KeepAnchor);
834 selections.append(sel);
835 } else if (m_visualMode == VisualLineMode) {
836 sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
837 sel.cursor.movePosition(StartOfLine, MoveAnchor);
838 sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
839 sel.cursor.movePosition(EndOfLine, KeepAnchor);
840 selections.append(sel);
841 } else if (m_visualMode == VisualBlockMode) {
842 QTextCursor tc = m_tc;
843 tc.setPosition(anchorPos);
844 int anchorColumn = tc.columnNumber();
845 int cursorColumn = m_tc.columnNumber();
846 int anchorRow = tc.blockNumber();
847 int cursorRow = m_tc.blockNumber();
848 int startColumn = qMin(anchorColumn, cursorColumn);
849 int endColumn = qMax(anchorColumn, cursorColumn);
850 int diffRow = cursorRow - anchorRow;
851 if (anchorRow > cursorRow) {
852 tc.setPosition(cursorPos);
855 tc.movePosition(StartOfLine, MoveAnchor);
856 for (int i = 0; i <= diffRow; ++i) {
857 if (startColumn < tc.block().length() - 1) {
858 int last = qMin(tc.block().length(), endColumn + 1);
859 int len = last - startColumn;
861 sel.cursor.movePosition(Right, MoveAnchor, startColumn);
862 sel.cursor.movePosition(Right, KeepAnchor, len);
863 selections.append(sel);
865 tc.movePosition(Down, MoveAnchor, 1);
869 //qDebug() << "SELECTION: " << selections;
870 emit q->selectionChanged(selections);
873 void FakeVimHandler::Private::updateMiniBuffer()
877 msg = "-- PASSING -- ";
878 } else if (!m_currentMessage.isEmpty()) {
879 msg = m_currentMessage;
880 } else if (m_mode == CommandMode && m_visualMode != NoVisualMode) {
881 if (m_visualMode == VisualCharMode) {
882 msg = "-- VISUAL --";
883 } else if (m_visualMode == VisualLineMode) {
884 msg = "-- VISUAL LINE --";
885 } else if (m_visualMode == VisualBlockMode) {
886 msg = "-- VISUAL BLOCK --";
888 } else if (m_mode == InsertMode) {
889 if (m_submode == ReplaceSubMode)
890 msg = "-- REPLACE --";
892 msg = "-- INSERT --";
894 if (m_mode == SearchForwardMode)
896 else if (m_mode == SearchBackwardMode)
898 else if (m_mode == ExMode)
900 foreach (QChar c, m_commandBuffer) {
901 if (c.unicode() < 32) {
903 msg += QChar(c.unicode() + 64);
908 if (!msg.isEmpty() && m_mode != CommandMode)
909 msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
912 emit q->commandBufferChanged(msg);
914 int linesInDoc = linesInDocument();
915 int l = cursorLineInDocument();
917 const QString pos = QString::fromLatin1("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
918 // FIXME: physical "-" logical
919 if (linesInDoc != 0) {
920 status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
922 status = FakeVimHandler::tr("%1All").arg(pos, -10);
924 emit q->statusDataChanged(status);
927 void FakeVimHandler::Private::showRedMessage(const QString &msg)
929 //qDebug() << "MSG: " << msg;
930 m_currentMessage = msg;
934 void FakeVimHandler::Private::showBlackMessage(const QString &msg)
936 //qDebug() << "MSG: " << msg;
937 m_commandBuffer = msg;
941 void FakeVimHandler::Private::notImplementedYet()
943 qDebug() << "Not implemented in FakeVim";
944 showRedMessage(FakeVimHandler::tr("Not implemented in FakeVim"));
948 EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
951 EventResult handled = EventHandled;
953 if (m_submode == WindowSubMode) {
954 emit q->windowCommandRequested(key);
955 m_submode = NoSubMode;
956 } else if (m_submode == RegisterSubMode) {
958 m_submode = NoSubMode;
959 m_rangemode = RangeLineMode;
960 } else if (m_submode == ChangeSubMode && key == 'c') { // tested
961 moveDown(count() - 1);
968 m_movetype = MoveLineWise;
969 m_lastInsertion.clear();
970 setDotCommand("%1cc", count());
972 } else if (m_submode == DeleteSubMode && key == 'd') { // tested
973 m_movetype = MoveLineWise;
974 int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
975 Range range(position(), endPos, RangeLineMode);
978 setDotCommand("%1dd", count());
979 m_submode = NoSubMode;
980 moveToFirstNonBlankOnLine();
983 } else if (m_submode == ShiftLeftSubMode && key == '<') {
985 moveDown(count() - 1);
986 m_movetype = MoveLineWise;
987 setDotCommand("%1<<", count());
989 } else if (m_submode == ShiftRightSubMode && key == '>') {
991 moveDown(count() - 1);
992 m_movetype = MoveLineWise;
993 setDotCommand("%1>>", count());
995 } else if (m_submode == IndentSubMode && key == '=') {
997 moveDown(count() - 1);
998 m_movetype = MoveLineWise;
999 setDotCommand("%1==", count());
1001 } else if (m_submode == ZSubMode) {
1002 //qDebug() << "Z_MODE " << cursorLineInDocument() << linesOnScreen();
1003 if (key == Key_Return || key == 't') { // cursor line to top of window
1004 if (!m_mvcount.isEmpty())
1005 setPosition(firstPositionInLine(count()));
1006 scrollUp(- cursorLineOnScreen());
1007 if (key == Key_Return)
1008 moveToFirstNonBlankOnLine();
1010 } else if (key == '.' || key == 'z') { // cursor line to center of window
1011 if (!m_mvcount.isEmpty())
1012 setPosition(firstPositionInLine(count()));
1013 scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
1015 moveToFirstNonBlankOnLine();
1017 } else if (key == '-' || key == 'b') { // cursor line to bottom of window
1018 if (!m_mvcount.isEmpty())
1019 setPosition(firstPositionInLine(count()));
1020 scrollUp(linesOnScreen() - cursorLineOnScreen());
1022 moveToFirstNonBlankOnLine();
1025 qDebug() << "IGNORED Z_MODE " << key << text;
1027 m_submode = NoSubMode;
1028 } else if (m_submode == CapitalZSubMode) {
1029 // Recognize ZZ and ZQ as aliases for ":x" and ":q!".
1030 m_submode = NoSubMode;
1033 else if (key == 'Q')
1034 handleCommand("q!");
1035 } else if (m_subsubmode == FtSubSubMode) {
1036 m_semicolonType = m_subsubdata;
1037 m_semicolonKey = key;
1039 m_subsubmode = NoSubSubMode;
1040 finishMovement(QString("%1%2%3")
1042 .arg(QChar(m_semicolonType))
1043 .arg(QChar(m_semicolonKey)));
1044 } else if (m_submode == ReplaceSubMode) {
1045 if (count() <= (rightDist() + atEndOfLine()) && text.size() == 1
1046 && (text.at(0).isPrint() || text.at(0).isSpace())) {
1051 removeSelectedText();
1052 m_tc.insertText(QString(count(), text.at(0)));
1053 m_movetype = MoveExclusive;
1054 setDotCommand("%1r" + text, count());
1057 m_submode = NoSubMode;
1059 } else if (m_subsubmode == MarkSubSubMode) {
1060 m_marks[key] = m_tc.position();
1061 m_subsubmode = NoSubSubMode;
1062 } else if (m_subsubmode == BackTickSubSubMode
1063 || m_subsubmode == TickSubSubMode) {
1064 if (m_marks.contains(key)) {
1065 setPosition(m_marks[key]);
1066 if (m_subsubmode == TickSubSubMode)
1067 moveToFirstNonBlankOnLine();
1070 showRedMessage(msgE20MarkNotSet(text));
1072 m_subsubmode = NoSubSubMode;
1073 } else if (key >= '0' && key <= '9') {
1074 if (key == '0' && m_mvcount.isEmpty()) {
1075 moveToStartOfLine();
1079 m_mvcount.append(QChar(key));
1081 } else if (key == '^') {
1082 moveToFirstNonBlankOnLine();
1084 } else if (0 && key == ',') {
1085 // FIXME: fakevim uses ',' by itself, so it is incompatible
1086 m_subsubmode = FtSubSubMode;
1087 // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
1088 m_subsubdata = m_semicolonType ^ 32;
1089 handleFfTt(m_semicolonKey);
1090 m_subsubmode = NoSubSubMode;
1092 } else if (key == ';') {
1093 m_subsubmode = FtSubSubMode;
1094 m_subsubdata = m_semicolonType;
1095 handleFfTt(m_semicolonKey);
1096 m_subsubmode = NoSubSubMode;
1098 } else if (key == ':') {
1100 m_currentMessage.clear();
1101 m_commandBuffer.clear();
1102 if (m_visualMode != NoVisualMode)
1103 m_commandBuffer = "'<,'>";
1104 m_commandHistory.append(QString());
1105 m_commandHistoryIndex = m_commandHistory.size() - 1;
1107 } else if (key == '/' || key == '?') {
1108 if (hasConfig(ConfigIncSearch)) {
1109 // re-use the core dialog.
1110 emit q->findRequested(key == '?');
1112 // FIXME: make core find dialog sufficiently flexible to
1113 // produce the "default vi" behaviour too. For now, roll our own.
1114 enterExMode(); // to get the cursor disabled
1115 m_currentMessage.clear();
1116 m_mode = (key == '/') ? SearchForwardMode : SearchBackwardMode;
1117 m_commandBuffer.clear();
1118 m_searchHistory.append(QString());
1119 m_searchHistoryIndex = m_searchHistory.size() - 1;
1122 } else if (key == '`') {
1123 m_subsubmode = BackTickSubSubMode;
1124 } else if (key == '#' || key == '*') {
1125 // FIXME: That's not proper vim behaviour
1126 m_tc.select(QTextCursor::WordUnderCursor);
1127 QString needle = "\\<" + m_tc.selection().toPlainText() + "\\>";
1128 m_searchHistory.append(needle);
1129 m_lastSearchForward = (key == '*');
1131 search(needle, m_lastSearchForward);
1133 } else if (key == '\'') {
1134 m_subsubmode = TickSubSubMode;
1135 } else if (key == '|') {
1136 moveToStartOfLine();
1137 moveRight(qMin(count(), rightDist()) - 1);
1140 } else if (key == '!' && m_visualMode == NoVisualMode) {
1141 m_submode = FilterSubMode;
1142 } else if (key == '!' && m_visualMode != NoVisualMode) {
1144 m_currentMessage.clear();
1145 m_commandBuffer = "'<,'>!";
1146 m_commandHistory.append(QString());
1147 m_commandHistoryIndex = m_commandHistory.size() - 1;
1149 } else if (key == '"') {
1150 m_submode = RegisterSubMode;
1151 } else if (unmodified == Key_Return) {
1152 moveToStartOfLine();
1154 moveToFirstNonBlankOnLine();
1156 } else if (key == '-') {
1157 moveToStartOfLine();
1159 moveToFirstNonBlankOnLine();
1161 } else if (key == Key_Home) {
1162 moveToStartOfLine();
1165 } else if (key == '$' || key == Key_End) {
1166 int submode = m_submode;
1168 m_movetype = MoveExclusive;
1170 if (submode == NoSubMode)
1171 m_targetColumn = -1;
1172 finishMovement("$");
1173 } else if (key == ',') {
1174 // FIXME: use some other mechanism
1176 m_passing = !m_passing;
1178 } else if (key == '.') {
1179 //qDebug() << "REPEATING" << quoteUnprintable(m_dotCommand);
1180 QString savedCommand = m_dotCommand;
1181 m_dotCommand.clear();
1182 replay(savedCommand, count());
1184 m_dotCommand = savedCommand;
1185 } else if (key == '<' && m_visualMode == NoVisualMode) {
1186 m_submode = ShiftLeftSubMode;
1187 } else if (key == '<' && m_visualMode != NoVisualMode) {
1190 } else if (key == '>' && m_visualMode == NoVisualMode) {
1191 m_submode = ShiftRightSubMode;
1192 } else if (key == '>' && m_visualMode != NoVisualMode) {
1193 shiftRegionRight(1);
1195 } else if (key == '=' && m_visualMode == NoVisualMode) {
1196 m_submode = IndentSubMode;
1197 } else if (key == '=' && m_visualMode != NoVisualMode) {
1200 } else if (key == '%') {
1201 m_movetype = MoveExclusive;
1202 moveToMatchingParanthesis();
1204 } else if (key == 'a') {
1206 m_lastInsertion.clear();
1210 } else if (key == 'A') {
1214 m_lastInsertion.clear();
1215 } else if (key == control('a')) {
1216 // FIXME: eat it to prevent the global "select all" shortcut to trigger
1217 } else if (key == 'b') {
1218 m_movetype = MoveExclusive;
1219 moveToWordBoundary(false, false);
1221 } else if (key == 'B') {
1222 m_movetype = MoveExclusive;
1223 moveToWordBoundary(true, false);
1225 } else if (key == 'c' && m_visualMode == NoVisualMode) {
1227 m_submode = ChangeSubMode;
1228 } else if (key == 'c' && m_visualMode == VisualCharMode) {
1230 m_submode = ChangeSubMode;
1232 } else if (key == 'C') {
1236 removeSelectedText();
1240 } else if (key == control('c')) {
1241 showBlackMessage("Type Alt-v,Alt-v to quit FakeVim mode");
1242 } else if (key == 'd' && m_visualMode == NoVisualMode) {
1243 if (m_rangemode == RangeLineMode) {
1244 m_savedYankPosition = m_tc.position();
1247 setPosition(m_savedYankPosition);
1253 m_opcount = m_mvcount;
1255 m_submode = DeleteSubMode;
1256 } else if ((key == 'd' || key == 'x') && m_visualMode == VisualCharMode) {
1258 m_submode = DeleteSubMode;
1260 } else if ((key == 'd' || key == 'x') && m_visualMode == VisualLineMode) {
1262 m_rangemode = RangeLineMode;
1264 removeSelectedText();
1265 } else if ((key == 'd' || key == 'x') && m_visualMode == VisualBlockMode) {
1267 m_rangemode = RangeBlockMode;
1269 removeSelectedText();
1270 setPosition(qMin(position(), anchor()));
1271 } else if (key == 'D') {
1273 m_submode = DeleteSubMode;
1274 moveDown(qMax(count() - 1, 0));
1275 m_movetype = MoveExclusive;
1279 } else if (key == control('d')) {
1280 int sline = cursorLineOnScreen();
1281 // FIXME: this should use the "scroll" option, and "count"
1282 moveDown(linesOnScreen() / 2);
1283 handleStartOfLine();
1284 scrollToLineInDocument(cursorLineInDocument() - sline);
1286 } else if (key == 'e') { // tested
1287 m_movetype = MoveInclusive;
1288 moveToWordBoundary(false, true);
1289 finishMovement("e");
1290 } else if (key == 'E') {
1291 m_movetype = MoveInclusive;
1292 moveToWordBoundary(true, true);
1294 } else if (key == control('e')) {
1295 // FIXME: this should use the "scroll" option, and "count"
1296 if (cursorLineOnScreen() == 0)
1300 } else if (key == 'f') {
1301 m_subsubmode = FtSubSubMode;
1302 m_movetype = MoveInclusive;
1304 } else if (key == 'F') {
1305 m_subsubmode = FtSubSubMode;
1306 m_movetype = MoveExclusive;
1308 } else if (key == 'g') {
1311 m_tc.setPosition(firstPositionInLine(1), KeepAnchor);
1312 handleStartOfLine();
1317 } else if (key == 'G') {
1318 int n = m_mvcount.isEmpty() ? linesInDocument() : count();
1319 m_tc.setPosition(firstPositionInLine(n), KeepAnchor);
1320 handleStartOfLine();
1322 } else if (key == 'h' || key == Key_Left
1323 || key == Key_Backspace || key == control('h')) {
1324 int n = qMin(count(), leftDist());
1325 if (m_fakeEnd && m_tc.block().length() > 1)
1329 finishMovement("h");
1330 } else if (key == 'H') {
1331 m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
1332 moveDown(qMax(count() - 1, 0));
1333 handleStartOfLine();
1335 } else if (key == 'i' || key == Key_Insert) {
1336 setDotCommand("i"); // setDotCommand("%1i", count());
1341 } else if (key == 'I') {
1342 setDotCommand("I"); // setDotCommand("%1I", count());
1345 moveToStartOfLine();
1347 moveToFirstNonBlankOnLine();
1348 m_tc.clearSelection();
1349 } else if (key == control('i')) {
1350 if (!m_jumpListRedo.isEmpty()) {
1351 m_jumpListUndo.append(cursorPosition());
1352 setCursorPosition(m_jumpListRedo.last());
1353 m_jumpListRedo.pop_back();
1355 } else if (key == 'j' || key == Key_Down) {
1356 if (m_submode == NoSubMode || m_submode == ZSubMode
1357 || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1360 m_movetype = MoveLineWise;
1361 moveToStartOfLine();
1363 moveDown(count() + 1);
1365 finishMovement("j");
1366 } else if (key == 'J') {
1367 if (m_submode == NoSubMode) {
1368 for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
1372 while (characterAtCursor() == ' ')
1374 removeSelectedText();
1376 m_tc.insertText(" ");
1381 } else if (key == 'k' || key == Key_Up) {
1382 if (m_submode == NoSubMode || m_submode == ZSubMode
1383 || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1386 m_movetype = MoveLineWise;
1387 moveToStartOfLine();
1390 moveUp(count() + 1);
1392 finishMovement("k");
1393 } else if (key == 'l' || key == Key_Right || key == ' ') {
1394 m_movetype = MoveExclusive;
1395 moveRight(qMin(count(), rightDist()));
1397 finishMovement("l");
1398 } else if (key == 'L') {
1399 m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
1400 moveUp(qMax(count(), 1));
1401 handleStartOfLine();
1403 } else if (key == control('l')) {
1404 // screen redraw. should not be needed
1405 } else if (key == 'm') {
1406 m_subsubmode = MarkSubSubMode;
1407 } else if (key == 'M') {
1408 m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
1409 handleStartOfLine();
1411 } else if (key == 'n') { // FIXME: see comment for '/'
1412 if (hasConfig(ConfigIncSearch))
1413 emit q->findNextRequested(false);
1415 search(lastSearchString(), m_lastSearchForward);
1417 } else if (key == 'N') {
1418 if (hasConfig(ConfigIncSearch))
1419 emit q->findNextRequested(true);
1421 search(lastSearchString(), !m_lastSearchForward);
1423 } else if (key == 'o' || key == 'O') {
1425 setDotCommand("%1o", count());
1427 moveToFirstNonBlankOnLine();
1431 m_tc.insertText("\n");
1432 insertAutomaticIndentation(key == 'o');
1434 } else if (key == control('o')) {
1435 if (!m_jumpListUndo.isEmpty()) {
1436 m_jumpListRedo.append(cursorPosition());
1437 setCursorPosition(m_jumpListUndo.last());
1438 m_jumpListUndo.pop_back();
1440 } else if (key == 'p' || key == 'P') {
1441 pasteText(key == 'p');
1443 setDotCommand("%1p", count());
1445 } else if (key == 'r') {
1446 m_submode = ReplaceSubMode;
1448 } else if (key == 'R') {
1449 // FIXME: right now we repeat the insertion count() times,
1450 // but not the deletion
1451 m_lastInsertion.clear();
1453 m_submode = ReplaceSubMode;
1455 } else if (key == control('r')) {
1457 } else if (key == 's') {
1461 moveRight(qMin(count(), rightDist()));
1463 removeSelectedText();
1464 setDotCommand("%1s", count());
1468 } else if (key == 't') {
1469 m_movetype = MoveInclusive;
1470 m_subsubmode = FtSubSubMode;
1472 } else if (key == 'T') {
1473 m_movetype = MoveExclusive;
1474 m_subsubmode = FtSubSubMode;
1476 } else if (key == 'u') {
1478 } else if (key == control('u')) {
1479 int sline = cursorLineOnScreen();
1480 // FIXME: this should use the "scroll" option, and "count"
1481 moveUp(linesOnScreen() / 2);
1482 handleStartOfLine();
1483 scrollToLineInDocument(cursorLineInDocument() - sline);
1485 } else if (key == 'v') {
1486 enterVisualMode(VisualCharMode);
1487 } else if (key == 'V') {
1488 enterVisualMode(VisualLineMode);
1489 } else if (key == control('v')) {
1490 enterVisualMode(VisualBlockMode);
1491 } else if (key == 'w') { // tested
1492 // Special case: "cw" and "cW" work the same as "ce" and "cE" if the
1493 // cursor is on a non-blank.
1494 if (m_submode == ChangeSubMode) {
1495 moveToWordBoundary(false, true);
1496 m_movetype = MoveInclusive;
1498 moveToNextWord(false);
1499 m_movetype = MoveExclusive;
1501 finishMovement("w");
1502 } else if (key == 'W') {
1503 if (m_submode == ChangeSubMode) {
1504 moveToWordBoundary(true, true);
1505 m_movetype = MoveInclusive;
1507 moveToNextWord(true);
1508 m_movetype = MoveExclusive;
1510 finishMovement("W");
1511 } else if (key == control('w')) {
1512 m_submode = WindowSubMode;
1513 } else if (key == 'x' && m_visualMode == NoVisualMode) { // = "dl"
1514 m_movetype = MoveExclusive;
1518 m_submode = DeleteSubMode;
1519 moveRight(qMin(count(), rightDist()));
1520 setDotCommand("%1x", count());
1522 } else if (key == 'X') {
1523 if (leftDist() > 0) {
1525 moveLeft(qMin(count(), leftDist()));
1527 removeSelectedText();
1530 } else if ((m_submode == YankSubMode && key == 'y')
1531 || (key == 'Y' && m_visualMode == NoVisualMode)) {
1532 const int line = cursorLineInDocument() + 1;
1533 m_savedYankPosition = position();
1534 setAnchor(firstPositionInLine(line));
1535 setPosition(lastPositionInLine(line+count() - 1));
1537 showBlackMessage(QString("%1 lines yanked").arg(count()));
1538 m_rangemode = RangeLineMode;
1539 m_movetype = MoveLineWise;
1540 m_submode = YankSubMode;
1542 } else if (key == 'y' && m_visualMode == NoVisualMode) {
1543 if (m_rangemode == RangeLineMode) {
1544 m_savedYankPosition = position();
1545 setAnchor(firstPositionInLine(cursorLineInDocument() + 1));
1547 m_savedYankPosition = position();
1551 m_rangemode = RangeCharMode;
1553 m_submode = YankSubMode;
1554 } else if (key == 'y' && m_visualMode == VisualCharMode) {
1555 Range range(position(), anchor(), RangeCharMode);
1556 range.endPos++; // MoveInclusive
1557 yankText(range, m_register);
1558 setPosition(qMin(position(), anchor()));
1561 } else if ((key == 'y' && m_visualMode == VisualLineMode)
1562 || (key == 'Y' && m_visualMode == VisualLineMode)
1563 || (key == 'Y' && m_visualMode == VisualCharMode)) {
1564 m_rangemode = RangeLineMode;
1566 setPosition(qMin(position(), anchor()));
1567 moveToStartOfLine();
1570 } else if ((key == 'y' || key == 'Y') && m_visualMode == VisualBlockMode) {
1571 m_rangemode = RangeBlockMode;
1573 setPosition(qMin(position(), anchor()));
1576 } else if (key == 'z') {
1577 m_submode = ZSubMode;
1578 } else if (key == 'Z') {
1579 m_submode = CapitalZSubMode;
1580 } else if (key == '~' && !atEndOfLine()) {
1583 moveRight(qMin(count(), rightDist()));
1584 QString str = selectedText();
1585 removeSelectedText();
1586 for (int i = str.size(); --i >= 0; ) {
1587 QChar c = str.at(i);
1588 str[i] = c.isUpper() ? c.toLower() : c.toUpper();
1590 m_tc.insertText(str);
1592 } else if (key == Key_PageDown || key == control('f')) {
1593 moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
1594 scrollToLineInDocument(cursorLineInDocument());
1595 handleStartOfLine();
1597 } else if (key == Key_PageUp || key == control('b')) {
1598 moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
1599 scrollToLineInDocument(cursorLineInDocument() + linesOnScreen() - 2);
1600 handleStartOfLine();
1602 } else if (key == Key_Delete) {
1604 moveRight(qMin(1, rightDist()));
1605 removeSelectedText();
1606 } else if (key == Key_Escape) {
1607 if (m_visualMode != NoVisualMode) {
1609 } else if (m_submode != NoSubMode) {
1610 m_submode = NoSubMode;
1611 m_subsubmode = NoSubSubMode;
1615 qDebug() << "IGNORED IN COMMAND MODE: " << key << text
1616 << " VISUAL: " << m_visualMode;
1617 handled = EventUnhandled;
1623 EventResult FakeVimHandler::Private::handleInsertMode(int key, int,
1624 const QString &text)
1626 if (key == Key_Escape || key == 27 || key == control('c')) {
1627 // start with '1', as one instance was already physically inserted
1629 QString data = m_lastInsertion;
1630 for (int i = 1; i < count(); ++i) {
1631 m_tc.insertText(m_lastInsertion);
1632 data += m_lastInsertion;
1634 moveLeft(qMin(1, leftDist()));
1636 m_dotCommand += m_lastInsertion;
1637 m_dotCommand += QChar(27);
1640 } else if (key == Key_Insert) {
1641 if (m_submode == ReplaceSubMode) {
1642 EDITOR(setCursorWidth(m_cursorWidth));
1643 EDITOR(setOverwriteMode(false));
1644 m_submode = NoSubMode;
1646 EDITOR(setCursorWidth(m_cursorWidth));
1647 EDITOR(setOverwriteMode(true));
1648 m_submode = ReplaceSubMode;
1650 } else if (key == Key_Left) {
1653 m_lastInsertion.clear();
1654 } else if (key == Key_Down) {
1655 //removeAutomaticIndentation();
1656 m_submode = NoSubMode;
1658 m_lastInsertion.clear();
1659 } else if (key == Key_Up) {
1660 //removeAutomaticIndentation();
1661 m_submode = NoSubMode;
1663 m_lastInsertion.clear();
1664 } else if (key == Key_Right) {
1667 m_lastInsertion.clear();
1668 } else if (key == Key_Return) {
1669 m_submode = NoSubMode;
1671 m_lastInsertion += "\n";
1672 insertAutomaticIndentation(true);
1674 } else if (key == Key_Backspace || key == control('h')) {
1675 if (!removeAutomaticIndentation())
1676 if (!m_lastInsertion.isEmpty() || hasConfig(ConfigBackspace, "start")) {
1677 m_tc.deletePreviousChar();
1678 m_lastInsertion.chop(1);
1681 } else if (key == Key_Delete) {
1683 m_lastInsertion.clear();
1684 } else if (key == Key_PageDown || key == control('f')) {
1685 removeAutomaticIndentation();
1686 moveDown(count() * (linesOnScreen() - 2));
1687 m_lastInsertion.clear();
1688 } else if (key == Key_PageUp || key == control('b')) {
1689 removeAutomaticIndentation();
1690 moveUp(count() * (linesOnScreen() - 2));
1691 m_lastInsertion.clear();
1692 } else if (key == Key_Tab && hasConfig(ConfigExpandTab)) {
1693 QString str = QString(theFakeVimSetting(ConfigTabStop)->value().toInt(), ' ');
1694 m_lastInsertion.append(str);
1695 m_tc.insertText(str);
1697 } else if (key >= control('a') && key <= control('z')) {
1699 } else if (!text.isEmpty()) {
1700 m_justAutoIndented = false;
1701 m_lastInsertion.append(text);
1702 if (m_submode == ReplaceSubMode) {
1704 m_submode = NoSubMode;
1708 m_tc.insertText(text);
1709 if (0 && hasConfig(ConfigAutoIndent) && isElectricCharacter(text.at(0))) {
1710 const QString leftText = m_tc.block().text()
1711 .left(m_tc.position() - 1 - m_tc.block().position());
1712 if (leftText.simplified().isEmpty())
1713 indentRegion(text.at(0));
1717 emit q->completionRequested();
1720 return EventUnhandled;
1723 return EventHandled;
1726 EventResult FakeVimHandler::Private::handleMiniBufferModes(int key, int unmodified,
1727 const QString &text)
1731 if (key == Key_Escape || key == control('c')) {
1732 m_commandBuffer.clear();
1735 } else if (key == Key_Backspace) {
1736 if (m_commandBuffer.isEmpty()) {
1739 m_commandBuffer.chop(1);
1742 } else if (key == Key_Left) {
1744 if (!m_commandBuffer.isEmpty())
1745 m_commandBuffer.chop(1);
1747 } else if (unmodified == Key_Return && m_mode == ExMode) {
1748 if (!m_commandBuffer.isEmpty()) {
1749 m_commandHistory.takeLast();
1750 m_commandHistory.append(m_commandBuffer);
1751 handleExCommand(m_commandBuffer);
1754 } else if (unmodified == Key_Return && isSearchMode()) {
1755 if (!m_commandBuffer.isEmpty()) {
1756 m_searchHistory.takeLast();
1757 m_searchHistory.append(m_commandBuffer);
1758 m_lastSearchForward = (m_mode == SearchForwardMode);
1759 search(lastSearchString(), m_lastSearchForward);
1764 } else if ((key == Key_Up || key == Key_PageUp) && isSearchMode()) {
1765 // FIXME: This and the three cases below are wrong as vim
1766 // takes only matching entries in the history into account.
1767 if (m_searchHistoryIndex > 0) {
1768 --m_searchHistoryIndex;
1769 showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
1771 } else if ((key == Key_Up || key == Key_PageUp) && m_mode == ExMode) {
1772 if (m_commandHistoryIndex > 0) {
1773 --m_commandHistoryIndex;
1774 showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
1776 } else if ((key == Key_Down || key == Key_PageDown) && isSearchMode()) {
1777 if (m_searchHistoryIndex < m_searchHistory.size() - 1) {
1778 ++m_searchHistoryIndex;
1779 showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
1781 } else if ((key == Key_Down || key == Key_PageDown) && m_mode == ExMode) {
1782 if (m_commandHistoryIndex < m_commandHistory.size() - 1) {
1783 ++m_commandHistoryIndex;
1784 showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
1786 } else if (key == Key_Tab) {
1787 m_commandBuffer += QChar(9);
1789 } else if (QChar(key).isPrint()) {
1790 m_commandBuffer += QChar(key);
1793 qDebug() << "IGNORED IN MINIBUFFER MODE: " << key << text;
1794 return EventUnhandled;
1796 return EventHandled;
1800 int FakeVimHandler::Private::readLineCode(QString &cmd)
1802 //qDebug() << "CMD: " << cmd;
1805 QChar c = cmd.at(0);
1808 return cursorLineInDocument() + 1;
1810 return linesInDocument();
1811 if (c == '\'' && !cmd.isEmpty()) {
1812 int mark = m_marks.value(cmd.at(0).unicode());
1814 showRedMessage(msgE20MarkNotSet(cmd.at(0)));
1819 return lineForPosition(mark);
1822 int n = readLineCode(cmd);
1823 return cursorLineInDocument() + 1 - (n == -1 ? 1 : n);
1826 int n = readLineCode(cmd);
1827 return cursorLineInDocument() + 1 + (n == -1 ? 1 : n);
1829 if (c == '\'' && !cmd.isEmpty()) {
1830 int pos = m_marks.value(cmd.at(0).unicode(), -1);
1831 //qDebug() << " MARK: " << cmd.at(0) << pos << lineForPosition(pos);
1833 showRedMessage(msgE20MarkNotSet(cmd.at(0)));
1838 return lineForPosition(pos);
1841 int n = c.unicode() - '0';
1842 while (!cmd.isEmpty()) {
1847 n = n * 10 + (c.unicode() - '0');
1849 //qDebug() << "N: " << n;
1857 void FakeVimHandler::Private::selectRange(int beginLine, int endLine)
1859 if (beginLine == -1)
1860 beginLine = cursorLineInDocument();
1862 endLine = cursorLineInDocument();
1863 if (beginLine > endLine)
1864 qSwap(beginLine, endLine);
1865 setAnchor(firstPositionInLine(beginLine));
1866 if (endLine == linesInDocument())
1867 setPosition(lastPositionInLine(endLine));
1869 setPosition(firstPositionInLine(endLine + 1));
1872 void FakeVimHandler::Private::handleCommand(const QString &cmd)
1874 m_tc = EDITOR(textCursor());
1875 handleExCommand(cmd);
1876 EDITOR(setTextCursor(m_tc));
1879 void FakeVimHandler::Private::handleExCommand(const QString &cmd0)
1882 if (cmd.startsWith(QLatin1Char('%')))
1883 cmd = "1,$" + cmd.mid(1);
1888 int line = readLineCode(cmd);
1892 if (cmd.startsWith(',')) {
1894 line = readLineCode(cmd);
1899 //qDebug() << "RANGE: " << beginLine << endLine << cmd << cmd0 << m_marks;
1901 static QRegExp reQuit("^qa?!?$");
1902 static QRegExp reDelete("^d( (.*))?$");
1903 static QRegExp reHistory("^his(tory)?( (.*))?$");
1904 static QRegExp reNormal("^norm(al)?( (.*))?$");
1905 static QRegExp reSet("^set?( (.*))?$");
1906 static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
1907 static QRegExp reSubstitute("^s(.)(.*)\\1(.*)\\1([gi]*)");
1910 showBlackMessage(QString());
1912 if (cmd.isEmpty()) {
1913 setPosition(firstPositionInLine(beginLine));
1914 showBlackMessage(QString());
1915 } else if (reDelete.indexIn(cmd) != -1) { // :d
1916 selectRange(beginLine, endLine);
1917 QString reg = reDelete.cap(2);
1918 QString text = selectedText();
1919 removeSelectedText();
1920 if (!reg.isEmpty()) {
1921 Register &r = m_registers[reg.at(0).unicode()];
1923 r.rangemode = RangeLineMode;
1925 } else if (reWrite.indexIn(cmd) != -1) { // :w and :x
1926 bool noArgs = (beginLine == -1);
1927 if (beginLine == -1)
1930 endLine = linesInDocument();
1931 //qDebug() << "LINES: " << beginLine << endLine;
1932 int indexOfSpace = cmd.indexOf(QChar(' '));
1934 if (indexOfSpace < 0)
1937 prefix = cmd.left(indexOfSpace);
1938 bool forced = prefix.contains(QChar('!'));
1939 bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x'));
1940 bool quitAll = quit && prefix.contains(QChar('a'));
1941 QString fileName = reWrite.cap(2);
1942 if (fileName.isEmpty())
1943 fileName = m_currentFileName;
1944 QFile file1(fileName);
1945 bool exists = file1.exists();
1946 if (exists && !forced && !noArgs) {
1947 showRedMessage(FakeVimHandler::tr("File '%1' exists (add ! to override)").arg(fileName));
1948 } else if (file1.open(QIODevice::ReadWrite)) {
1950 QTextCursor tc = m_tc;
1951 Range range(firstPositionInLine(beginLine),
1952 firstPositionInLine(endLine), RangeLineMode);
1953 QString contents = text(range);
1955 qDebug() << "LINES: " << beginLine << endLine;
1956 bool handled = false;
1957 emit q->writeFileRequested(&handled, fileName, contents);
1958 // nobody cared, so act ourselves
1960 //qDebug() << "HANDLING MANUAL SAVE TO " << fileName;
1961 QFile::remove(fileName);
1962 QFile file2(fileName);
1963 if (file2.open(QIODevice::ReadWrite)) {
1964 QTextStream ts(&file2);
1967 showRedMessage(FakeVimHandler::tr("Cannot open file '%1' for writing").arg(fileName));
1970 // check result by reading back
1971 QFile file3(fileName);
1972 file3.open(QIODevice::ReadOnly);
1973 QByteArray ba = file3.readAll();
1974 showBlackMessage(FakeVimHandler::tr("\"%1\" %2 %3L, %4C written")
1975 .arg(fileName).arg(exists ? " " : " [New] ")
1976 .arg(ba.count('\n')).arg(ba.size()));
1978 passUnknownExCommand(forced ? "qa!" : "qa");
1980 passUnknownExCommand(forced ? "q!" : "q");
1982 showRedMessage(FakeVimHandler::tr("Cannot open file '%1' for reading").arg(fileName));
1984 } else if (cmd.startsWith("r ")) { // :r
1985 m_currentFileName = cmd.mid(2);
1986 QFile file(m_currentFileName);
1987 file.open(QIODevice::ReadOnly);
1988 QTextStream ts(&file);
1989 QString data = ts.readAll();
1990 EDITOR(setPlainText(data));
1991 showBlackMessage(FakeVimHandler::tr("\"%1\" %2L, %3C")
1992 .arg(m_currentFileName).arg(data.count('\n')).arg(data.size()));
1993 } else if (cmd.startsWith(QLatin1Char('!'))) {
1994 selectRange(beginLine, endLine);
1995 QString command = cmd.mid(1).trimmed();
1996 QString text = selectedText();
1997 removeSelectedText();
1999 proc.start(cmd.mid(1));
2000 proc.waitForStarted();
2001 proc.write(text.toUtf8());
2002 proc.closeWriteChannel();
2003 proc.waitForFinished();
2004 QString result = QString::fromUtf8(proc.readAllStandardOutput());
2005 m_tc.insertText(result);
2007 setPosition(firstPositionInLine(beginLine));
2008 //qDebug() << "FILTER: " << command;
2009 showBlackMessage(FakeVimHandler::tr("%n lines filtered", 0, text.count('\n')));
2010 } else if (cmd.startsWith(QLatin1Char('>'))) {
2011 m_anchor = firstPositionInLine(beginLine);
2012 setPosition(firstPositionInLine(endLine));
2013 shiftRegionRight(1);
2015 showBlackMessage(FakeVimHandler::tr("%n lines >ed %1 time", 0, (endLine - beginLine + 1)).arg(1));
2016 } else if (cmd == "red" || cmd == "redo") { // :redo
2019 } else if (reNormal.indexIn(cmd) != -1) { // :normal
2020 //qDebug() << "REPLAY: " << reNormal.cap(3);
2021 replay(reNormal.cap(3), 1);
2022 } else if (reSubstitute.indexIn(cmd) != -1) { // :substitute
2023 QString needle = reSubstitute.cap(2);
2024 const QString replacement = reSubstitute.cap(3);
2025 QString flags = reSubstitute.cap(4);
2026 const bool startOfLineOnly = needle.startsWith('^');
2027 if (startOfLineOnly)
2028 needle.remove(0, 1);
2029 needle.replace('$', '\n');
2030 needle.replace("\\\n", "\\$");
2031 QRegExp pattern(needle);
2032 if (flags.contains('i'))
2033 pattern.setCaseSensitivity(Qt::CaseInsensitive);
2034 const bool global = flags.contains('g');
2036 for (int line = beginLine; line <= endLine; ++line) {
2037 const int start = firstPositionInLine(line);
2038 const int end = lastPositionInLine(line);
2039 for (int position = start; position <= end && position >= start; ) {
2040 position = pattern.indexIn(m_tc.document()->toPlainText(), position);
2041 if (startOfLineOnly && position != start)
2043 if (position != -1) {
2044 m_tc.setPosition(position);
2045 m_tc.movePosition(QTextCursor::NextCharacter,
2046 KeepAnchor, pattern.matchedLength());
2047 QString text = m_tc.selectedText();
2048 if (text.endsWith(ParagraphSeparator)) {
2049 text = replacement + "\n";
2051 text.replace(ParagraphSeparator, "\n");
2052 text.replace(pattern, replacement);
2054 m_tc.removeSelectedText();
2055 m_tc.insertText(text);
2062 } else if (reSet.indexIn(cmd) != -1) { // :set
2063 showBlackMessage(QString());
2064 QString arg = reSet.cap(2);
2065 SavedAction *act = theFakeVimSettings()->item(arg);
2066 if (arg.isEmpty()) {
2067 theFakeVimSetting(SettingsDialog)->trigger(QVariant());
2068 } else if (act && act->value().type() == QVariant::Bool) {
2069 // boolean config to be switched on
2070 bool oldValue = act->value().toBool();
2071 if (oldValue == false)
2072 act->setValue(true);
2073 else if (oldValue == true)
2076 // non-boolean to show
2077 showBlackMessage(arg + '=' + act->value().toString());
2078 } else if (arg.startsWith("no")
2079 && (act = theFakeVimSettings()->item(arg.mid(2)))) {
2080 // boolean config to be switched off
2081 bool oldValue = act->value().toBool();
2082 if (oldValue == true)
2083 act->setValue(false);
2084 else if (oldValue == false)
2086 } else if (arg.contains('=')) {
2087 // non-boolean config to set
2088 int p = arg.indexOf('=');
2089 act = theFakeVimSettings()->item(arg.left(p));
2091 act->setValue(arg.mid(p + 1));
2093 showRedMessage(FakeVimHandler::tr("E512: Unknown option: ") + arg);
2096 } else if (reHistory.indexIn(cmd) != -1) { // :history
2097 QString arg = reSet.cap(3);
2098 if (arg.isEmpty()) {
2100 info += "# command history\n";
2102 foreach (const QString &item, m_commandHistory) {
2104 info += QString("%1 %2\n").arg(i, -8).arg(item);
2106 emit q->extraInformationChanged(info);
2108 notImplementedYet();
2112 passUnknownExCommand(cmd);
2116 void FakeVimHandler::Private::passUnknownExCommand(const QString &cmd)
2118 emit q->handleExCommandRequested(cmd);
2121 static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *flags)
2123 // FIXME: Rough mapping of a common case
2124 if (needle->startsWith("\\<") && needle->endsWith("\\>"))
2125 (*flags) |= QTextDocument::FindWholeWords;
2126 needle->replace("\\<", ""); // start of word
2127 needle->replace("\\>", ""); // end of word
2128 //qDebug() << "NEEDLE " << needle0 << needle;
2131 void FakeVimHandler::Private::search(const QString &needle0, bool forward)
2133 showBlackMessage((forward ? '/' : '?') + needle0);
2134 CursorPosition origPosition = cursorPosition();
2135 QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2137 flags |= QTextDocument::FindBackward;
2139 QString needle = needle0;
2140 vimPatternToQtPattern(&needle, &flags);
2143 m_tc.movePosition(Right, MoveAnchor, 1);
2145 int oldLine = cursorLineInDocument() - cursorLineOnScreen();
2147 EDITOR(setTextCursor(m_tc));
2148 if (EDITOR(find(needle, flags))) {
2149 m_tc = EDITOR(textCursor());
2150 m_tc.setPosition(m_tc.anchor());
2151 // making this unconditional feels better, but is not "vim like"
2152 if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
2153 scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
2154 highlightMatches(needle);
2156 m_tc.setPosition(forward ? 0 : lastPositionInDocument());
2157 EDITOR(setTextCursor(m_tc));
2158 if (EDITOR(find(needle, flags))) {
2159 m_tc = EDITOR(textCursor());
2160 m_tc.setPosition(m_tc.anchor());
2161 if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
2162 scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
2164 showRedMessage(FakeVimHandler::tr("search hit BOTTOM, continuing at TOP"));
2166 showRedMessage(FakeVimHandler::tr("search hit TOP, continuing at BOTTOM"));
2167 highlightMatches(needle);
2169 highlightMatches(QString());
2170 setCursorPosition(origPosition);
2171 showRedMessage(FakeVimHandler::tr("Pattern not found: ") + needle);
2176 void FakeVimHandler::Private::highlightMatches(const QString &needle0)
2178 if (!hasConfig(ConfigHlSearch))
2180 if (needle0 == m_oldNeedle)
2182 m_oldNeedle = needle0;
2183 m_searchSelections.clear();
2185 if (!needle0.isEmpty()) {
2186 QTextCursor tc = m_tc;
2187 tc.movePosition(StartOfDocument, MoveAnchor);
2189 QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2190 QString needle = needle0;
2191 vimPatternToQtPattern(&needle, &flags);
2194 EDITOR(setTextCursor(tc));
2195 while (EDITOR(find(needle, flags))) {
2196 tc = EDITOR(textCursor());
2197 QTextEdit::ExtraSelection sel;
2199 sel.format = tc.blockCharFormat();
2200 sel.format.setBackground(QColor(177, 177, 0));
2201 m_searchSelections.append(sel);
2202 tc.movePosition(Right, MoveAnchor);
2203 EDITOR(setTextCursor(tc));
2209 void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
2211 QTextDocument *doc = m_tc.document();
2212 const QTextBlock &block = m_tc.block();
2213 int firstPos = block.position();
2214 for (int i = firstPos, n = firstPos + block.length(); i < n; ++i) {
2215 if (!doc->characterAt(i).isSpace()) {
2220 setPosition(block.position());
2223 void FakeVimHandler::Private::indentRegion(QChar typedChar)
2225 //int savedPos = anchor();
2226 int beginLine = lineForPosition(anchor());
2227 int endLine = lineForPosition(position());
2228 if (beginLine > endLine)
2229 qSwap(beginLine, endLine);
2232 emit q->indentRegion(&amount, beginLine, endLine, typedChar);
2234 setPosition(firstPositionInLine(beginLine));
2235 moveToFirstNonBlankOnLine();
2237 setDotCommand("%1==", endLine - beginLine + 1);
2240 void FakeVimHandler::Private::shiftRegionRight(int repeat)
2242 int beginLine = lineForPosition(anchor());
2243 int endLine = lineForPosition(position());
2244 if (beginLine > endLine)
2245 qSwap(beginLine, endLine);
2246 int len = config(ConfigShiftWidth).toInt() * repeat;
2247 QString indent(len, ' ');
2248 int firstPos = firstPositionInLine(beginLine);
2250 for (int line = beginLine; line <= endLine; ++line) {
2251 setPosition(firstPositionInLine(line));
2252 m_tc.insertText(indent);
2255 setPosition(firstPos);
2256 moveToFirstNonBlankOnLine();
2258 setDotCommand("%1>>", endLine - beginLine + 1);
2261 void FakeVimHandler::Private::shiftRegionLeft(int repeat)
2263 int beginLine = lineForPosition(anchor());
2264 int endLine = lineForPosition(position());
2265 if (beginLine > endLine)
2266 qSwap(beginLine, endLine);
2267 int shift = config(ConfigShiftWidth).toInt() * repeat;
2268 int tab = config(ConfigTabStop).toInt();
2269 int firstPos = firstPositionInLine(beginLine);
2271 for (int line = beginLine; line <= endLine; ++line) {
2272 int pos = firstPositionInLine(line);
2275 QString text = m_tc.block().text();
2278 for (; i < text.size() && amount <= shift; ++i) {
2279 if (text.at(i) == ' ')
2281 else if (text.at(i) == '\t')
2282 amount += tab; // FIXME: take position into consideration
2286 setPosition(pos + i);
2287 text = selectedText();
2288 removeSelectedText();
2292 setPosition(firstPos);
2293 moveToFirstNonBlankOnLine();
2295 setDotCommand("%1<<", endLine - beginLine + 1);
2298 void FakeVimHandler::Private::moveToTargetColumn()
2300 const QTextBlock &block = m_tc.block();
2301 int col = m_tc.position() - block.position();
2302 if (col == m_targetColumn)
2304 //qDebug() << "CORRECTING COLUMN FROM: " << col << "TO" << m_targetColumn;
2305 if (m_targetColumn == -1 || block.length() <= m_targetColumn)
2306 m_tc.setPosition(block.position() + block.length() - 1, MoveAnchor);
2308 m_tc.setPosition(block.position() + m_targetColumn, MoveAnchor);
2311 /* if simple is given:
2313 * class 1: non-spaces
2316 * class 1: non-space-or-letter-or-number
2317 * class 2: letter-or-number
2319 static int charClass(QChar c, bool simple)
2322 return c.isSpace() ? 0 : 1;
2323 if (c.isLetterOrNumber() || c.unicode() == '_')
2325 return c.isSpace() ? 0 : 1;
2328 void FakeVimHandler::Private::moveToWordBoundary(bool simple, bool forward)
2330 int repeat = count();
2331 QTextDocument *doc = m_tc.document();
2332 int n = forward ? lastPositionInDocument() : 0;
2335 QChar c = doc->characterAt(m_tc.position() + (forward ? 1 : -1));
2336 //qDebug() << "EXAMINING: " << c << " AT " << position();
2337 int thisClass = charClass(c, simple);
2338 if (thisClass != lastClass && lastClass != 0)
2342 lastClass = thisClass;
2343 if (m_tc.position() == n)
2345 forward ? moveRight() : moveLeft();
2350 void FakeVimHandler::Private::handleFfTt(int key)
2352 // m_subsubmode \in { 'f', 'F', 't', 'T' }
2353 bool forward = m_subsubdata == 'f' || m_subsubdata == 't';
2354 int repeat = count();
2355 QTextDocument *doc = m_tc.document();
2356 QTextBlock block = m_tc.block();
2357 int n = block.position();
2359 n += block.length();
2360 int pos = m_tc.position();
2362 pos += forward ? 1 : -1;
2365 int uc = doc->characterAt(pos).unicode();
2366 if (uc == ParagraphSeparator)
2371 if (m_subsubdata == 't')
2373 else if (m_subsubdata == 'T')
2377 m_tc.movePosition(Right, KeepAnchor, pos - m_tc.position());
2379 m_tc.movePosition(Left, KeepAnchor, m_tc.position() - pos);
2386 void FakeVimHandler::Private::moveToNextWord(bool simple)
2388 // FIXME: 'w' should stop on empty lines, too
2389 int repeat = count();
2390 int n = lastPositionInDocument();
2391 int lastClass = charClass(characterAtCursor(), simple);
2393 QChar c = characterAtCursor();
2394 int thisClass = charClass(c, simple);
2395 if (thisClass != lastClass && thisClass != 0)
2399 lastClass = thisClass;
2401 if (m_tc.position() == n)
2407 void FakeVimHandler::Private::moveToMatchingParanthesis()
2410 bool forward = false;
2412 emit q->moveToMatchingParenthesis(&moved, &forward, &m_tc);
2414 if (moved && forward) {
2415 if (m_submode == NoSubMode || m_submode == ZSubMode || m_submode == CapitalZSubMode || m_submode == RegisterSubMode)
2416 m_tc.movePosition(Left, KeepAnchor, 1);
2421 int FakeVimHandler::Private::cursorLineOnScreen() const
2425 QRect rect = EDITOR(cursorRect());
2426 return rect.y() / rect.height();
2429 int FakeVimHandler::Private::linesOnScreen() const
2433 QRect rect = EDITOR(cursorRect());
2434 return EDITOR(height()) / rect.height();
2437 int FakeVimHandler::Private::columnsOnScreen() const
2441 QRect rect = EDITOR(cursorRect());
2442 // qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
2443 return EDITOR(width()) / rect.width();
2446 int FakeVimHandler::Private::cursorLineInDocument() const
2448 return m_tc.block().blockNumber();
2451 int FakeVimHandler::Private::cursorColumnInDocument() const
2453 return m_tc.position() - m_tc.block().position();
2456 int FakeVimHandler::Private::linesInDocument() const
2458 return m_tc.isNull() ? 0 : m_tc.document()->blockCount();
2461 void FakeVimHandler::Private::scrollToLineInDocument(int line)
2463 // FIXME: works only for QPlainTextEdit
2464 QScrollBar *scrollBar = EDITOR(verticalScrollBar());
2465 //qDebug() << "SCROLL: " << scrollBar->value() << line;
2466 scrollBar->setValue(line);
2467 //QTC_ASSERT(firstVisibleLineInDocument() == line, /**/);
2470 int FakeVimHandler::Private::firstVisibleLineInDocument() const
2472 QScrollBar *scrollBar = EDITOR(verticalScrollBar());
2473 if (0 && scrollBar->value() != cursorLineInDocument() - cursorLineOnScreen()) {
2474 qDebug() << "SCROLLBAR: " << scrollBar->value()
2475 << "CURSORLINE IN DOC" << cursorLineInDocument()
2476 << "CURSORLINE ON SCREEN" << cursorLineOnScreen();
2478 //return scrollBar->value();
2479 return cursorLineInDocument() - cursorLineOnScreen();
2482 void FakeVimHandler::Private::scrollUp(int count)
2484 scrollToLineInDocument(cursorLineInDocument() - cursorLineOnScreen() - count);
2487 int FakeVimHandler::Private::lastPositionInDocument() const
2489 QTextBlock block = m_tc.document()->lastBlock();
2490 return block.position() + block.length() - 1;
2493 QString FakeVimHandler::Private::lastSearchString() const
2495 return m_searchHistory.empty() ? QString() : m_searchHistory.back();
2498 QString FakeVimHandler::Private::text(const Range &range) const
2500 if (range.rangemode == RangeCharMode) {
2501 QTextCursor tc = m_tc;
2502 tc.setPosition(range.beginPos, MoveAnchor);
2503 tc.setPosition(range.endPos, KeepAnchor);
2504 return tc.selection().toPlainText();
2506 if (range.rangemode == RangeLineMode) {
2507 QTextCursor tc = m_tc;
2508 tc.setPosition(firstPositionInLine(lineForPosition(range.beginPos)), MoveAnchor);
2509 tc.setPosition(firstPositionInLine(lineForPosition(range.endPos)+1), KeepAnchor);
2510 return tc.selection().toPlainText();
2512 // FIXME: Performance?
2513 int beginLine = lineForPosition(range.beginPos);
2514 int endLine = lineForPosition(range.endPos);
2515 int beginColumn = 0;
2516 int endColumn = INT_MAX;
2517 if (range.rangemode == RangeBlockMode) {
2518 int column1 = range.beginPos - firstPositionInLine(beginLine);
2519 int column2 = range.endPos - firstPositionInLine(endLine);
2520 beginColumn = qMin(column1, column2);
2521 endColumn = qMax(column1, column2);
2522 qDebug() << "COLS: " << beginColumn << endColumn;
2524 int len = endColumn - beginColumn + 1;
2526 QTextBlock block = m_tc.document()->findBlockByNumber(beginLine - 1);
2527 for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
2528 QString line = block.text();
2529 if (range.rangemode == RangeBlockMode) {
2530 line = line.mid(beginColumn, len);
2531 if (line.size() < len)
2532 line += QString(len - line.size(), QChar(' '));
2535 if (!contents.endsWith('\n'))
2537 block = block.next();
2539 //qDebug() << "SELECTED: " << contents;
2543 void FakeVimHandler::Private::yankSelectedText()
2545 Range range(anchor(), position());
2546 range.rangemode = m_rangemode;
2547 yankText(range, m_register);
2550 void FakeVimHandler::Private::yankText(const Range &range, int toregister)
2552 Register ® = m_registers[toregister];
2553 reg.contents = text(range);
2554 reg.rangemode = range.rangemode;
2555 //qDebug() << "YANKED: " << reg.contents;
2558 void FakeVimHandler::Private::removeSelectedText()
2560 Range range(anchor(), position());
2561 range.rangemode = m_rangemode;
2565 void FakeVimHandler::Private::removeText(const Range &range)
2567 QTextCursor tc = m_tc;
2568 switch (range.rangemode) {
2569 case RangeCharMode: {
2570 tc.setPosition(range.beginPos, MoveAnchor);
2571 tc.setPosition(range.endPos, KeepAnchor);
2572 fixMarks(range.beginPos, tc.selectionStart() - tc.selectionEnd());
2573 tc.removeSelectedText();
2576 case RangeLineMode: {
2577 tc.setPosition(range.beginPos, MoveAnchor);
2578 tc.movePosition(StartOfLine, MoveAnchor);
2579 tc.setPosition(range.endPos, KeepAnchor);
2580 tc.movePosition(EndOfLine, KeepAnchor);
2581 tc.movePosition(Right, KeepAnchor, 1);
2582 fixMarks(range.beginPos, tc.selectionStart() - tc.selectionEnd());
2583 tc.removeSelectedText();
2586 case RangeBlockMode: {
2587 int beginLine = lineForPosition(range.beginPos);
2588 int endLine = lineForPosition(range.endPos);
2589 int column1 = range.beginPos - firstPositionInLine(beginLine);
2590 int column2 = range.endPos - firstPositionInLine(endLine);
2591 int beginColumn = qMin(column1, column2);
2592 int endColumn = qMax(column1, column2);
2593 qDebug() << "COLS: " << beginColumn << endColumn;
2595 QTextBlock block = m_tc.document()->findBlockByNumber(endLine - 1);
2597 for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
2598 int bCol = qMin(beginColumn, block.length() - 1);
2599 int eCol = qMin(endColumn, block.length() - 1);
2600 tc.setPosition(block.position() + bCol, MoveAnchor);
2601 tc.setPosition(block.position() + eCol, KeepAnchor);
2602 fixMarks(block.position() + bCol, tc.selectionStart() - tc.selectionEnd());
2603 tc.removeSelectedText();
2604 block = block.previous();
2611 void FakeVimHandler::Private::pasteText(bool afterCursor)
2613 const QString text = m_registers[m_register].contents;
2614 const QStringList lines = text.split(QChar('\n'));
2615 switch (m_registers[m_register].rangemode) {
2616 case RangeCharMode: {
2618 for (int i = count(); --i >= 0; ) {
2619 if (afterCursor && rightDist() > 0)
2621 fixMarks(position(), text.length());
2622 m_tc.insertText(text);
2627 case RangeLineMode: {
2628 moveToStartOfLine();
2630 for (int i = count(); --i >= 0; ) {
2633 fixMarks(position(), text.length());
2634 m_tc.insertText(text);
2635 moveUp(lines.size() - 1);
2637 moveToFirstNonBlankOnLine();
2640 case RangeBlockMode: {
2642 QTextBlock block = m_tc.block();
2644 QTextCursor tc = m_tc;
2645 const int col = tc.position() - block.position();
2646 //for (int i = lines.size(); --i >= 0; ) {
2647 for (int i = 0; i < lines.size(); ++i) {
2648 const QString line = lines.at(i);
2649 tc.movePosition(StartOfLine, MoveAnchor);
2650 if (col >= block.length()) {
2651 tc.movePosition(EndOfLine, MoveAnchor);
2652 fixMarks(position(), QString(col - line.size() + 1, QChar(' ')).length());
2653 tc.insertText(QString(col - line.size() + 1, QChar(' ')));
2655 tc.movePosition(Right, MoveAnchor, col);
2657 qDebug() << "INSERT " << line << " AT " << tc.position()
2659 fixMarks(position(), line.length());
2660 tc.insertText(line);
2661 tc.movePosition(StartOfLine, MoveAnchor);
2662 tc.movePosition(Down, MoveAnchor, 1);
2663 if (tc.position() >= lastPositionInDocument() - 1) {
2664 fixMarks(position(), QString(QChar('\n')).length());
2665 tc.insertText(QString(QChar('\n')));
2666 tc.movePosition(Up, MoveAnchor, 1);
2668 block = block.next();
2676 //FIXME: This needs to called after undo/insert
2677 void FakeVimHandler::Private::fixMarks(int positionAction, int positionChange)
2679 QHashIterator<int, int> i(m_marks);
2680 while (i.hasNext()) {
2682 if (i.value() >= positionAction) {
2683 if (i.value() + positionChange > 0)
2684 m_marks[i.key()] = i.value() + positionChange;
2686 m_marks.remove(i.key());
2691 int FakeVimHandler::Private::firstPositionInLine(int line) const
2693 return m_tc.document()->findBlockByNumber(line - 1).position();
2696 int FakeVimHandler::Private::lastPositionInLine(int line) const
2698 QTextBlock block = m_tc.document()->findBlockByNumber(line - 1);
2699 return block.position() + block.length() - 1;
2702 int FakeVimHandler::Private::lineForPosition(int pos) const
2704 QTextCursor tc = m_tc;
2705 tc.setPosition(pos);
2706 return tc.block().blockNumber() + 1;
2709 void FakeVimHandler::Private::enterVisualMode(VisualMode visualMode)
2712 m_visualMode = visualMode;
2713 m_marks['<'] = m_tc.position();
2714 m_marks['>'] = m_tc.position();
2719 void FakeVimHandler::Private::leaveVisualMode()
2721 m_visualMode = NoVisualMode;
2726 QWidget *FakeVimHandler::Private::editor() const
2729 ? static_cast<QWidget *>(m_textedit)
2730 : static_cast<QWidget *>(m_plaintextedit);
2733 void FakeVimHandler::Private::undo()
2735 int current = m_tc.document()->availableUndoSteps();
2739 int rev = m_tc.document()->availableUndoSteps();
2741 showBlackMessage(FakeVimHandler::tr("Already at oldest change"));
2743 showBlackMessage(QString());
2745 if (m_undoCursorPosition.contains(rev))
2746 m_tc.setPosition(m_undoCursorPosition[rev]);
2749 void FakeVimHandler::Private::redo()
2751 int current = m_tc.document()->availableUndoSteps();
2755 int rev = m_tc.document()->availableUndoSteps();
2757 showBlackMessage(FakeVimHandler::tr("Already at newest change"));
2759 showBlackMessage(QString());
2761 if (m_undoCursorPosition.contains(rev))
2762 m_tc.setPosition(m_undoCursorPosition[rev]);
2765 void FakeVimHandler::Private::enterInsertMode()
2767 EDITOR(setCursorWidth(m_cursorWidth));
2768 EDITOR(setOverwriteMode(false));
2769 m_mode = InsertMode;
2770 m_lastInsertion.clear();
2773 void FakeVimHandler::Private::enterCommandMode()
2775 EDITOR(setCursorWidth(m_cursorWidth));
2776 EDITOR(setOverwriteMode(true));
2777 m_mode = CommandMode;
2780 void FakeVimHandler::Private::enterExMode()
2782 EDITOR(setCursorWidth(0));
2783 EDITOR(setOverwriteMode(false));
2787 void FakeVimHandler::Private::recordJump()
2789 m_jumpListUndo.append(cursorPosition());
2790 m_jumpListRedo.clear();
2791 UNDO_DEBUG("jumps: " << m_jumpListUndo);
2794 void FakeVimHandler::Private::recordNewUndo()
2797 UNDO_DEBUG("---- BREAK ----");
2801 void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
2803 if (!hasConfig(ConfigAutoIndent))
2805 QTextBlock block = goingDown ? m_tc.block().previous() : m_tc.block().next();
2806 QString text = block.text();
2807 int pos = 0, n = text.size();
2808 while (pos < n && text.at(pos).isSpace())
2811 // FIXME: handle 'smartindent' and 'cindent'
2812 m_tc.insertText(text);
2813 m_justAutoIndented = text.size();
2816 bool FakeVimHandler::Private::removeAutomaticIndentation()
2818 if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
2820 m_tc.movePosition(StartOfLine, KeepAnchor);
2821 m_tc.removeSelectedText();
2822 m_lastInsertion.chop(m_justAutoIndented);
2823 m_justAutoIndented = 0;
2827 void FakeVimHandler::Private::handleStartOfLine()
2829 if (hasConfig(ConfigStartOfLine))
2830 moveToFirstNonBlankOnLine();
2833 void FakeVimHandler::Private::replay(const QString &command, int n)
2835 //qDebug() << "REPLAY: " << command;
2837 for (int i = n; --i >= 0; ) {
2838 foreach (QChar c, command) {
2839 //qDebug() << " REPLAY: " << QString(c);
2840 handleKey(c.unicode(), c.unicode(), QString(c));
2846 ///////////////////////////////////////////////////////////////////////
2850 ///////////////////////////////////////////////////////////////////////
2852 FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
2853 : QObject(parent), d(new Private(this, widget))
2856 FakeVimHandler::~FakeVimHandler()
2861 bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
2863 bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
2865 if (active && ev->type() == QEvent::KeyPress && ob == d->editor()) {
2866 QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
2867 KEY_DEBUG("KEYPRESS" << kev->key());
2868 EventResult res = d->handleEvent(kev);
2869 // returning false core the app see it
2870 //KEY_DEBUG("HANDLED CODE:" << res);
2871 //return res != EventPassedToCore;
2873 return res == EventHandled;
2876 if (active && ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
2877 QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
2878 if (d->wantsOverride(kev)) {
2879 KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
2880 ev->accept(); // accepting means "don't run the shortcuts"
2883 KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
2887 return QObject::eventFilter(ob, ev);
2890 void FakeVimHandler::installEventFilter()
2892 d->installEventFilter();
2895 void FakeVimHandler::setupWidget()
2900 void FakeVimHandler::restoreWidget()
2905 void FakeVimHandler::handleCommand(const QString &cmd)
2907 d->handleCommand(cmd);
2910 void FakeVimHandler::setCurrentFileName(const QString &fileName)
2912 d->m_currentFileName = fileName;
2915 void FakeVimHandler::showBlackMessage(const QString &msg)
2917 d->showBlackMessage(msg);
2920 void FakeVimHandler::showRedMessage(const QString &msg)
2922 d->showRedMessage(msg);
2925 QWidget *FakeVimHandler::widget()
2930 } // namespace Internal
2931 } // namespace FakeVim