OSDN Git Service

fakevim: fix undo() behaviour
[qt-creator-jp/qt-creator-jp.git] / src / plugins / fakevim / fakevimhandler.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** Commercial Usage
10 **
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.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
25 ** If you are unsure which license is appropriate for your use, please
26 ** contact the sales department at http://qt.nokia.com/contact.
27 **
28 **************************************************************************/
29
30 #include "fakevimhandler.h"
31
32 //
33 // ATTENTION:
34 //
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.
39 //
40 // 2 There are a few auto tests located in ../../../tests/auto/fakevim.
41 //   Commands that are covered there are marked as "// tested" below.
42 //
43 // 3 Some conventions:
44 //
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'
47 //   concepts.
48 //
49 //   Do not pass QTextCursor etc around unless really needed. Convert
50 //   early to  line/column.
51 //
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.
55 //
56
57 #include <utils/qtcassert.h>
58
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>
68
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>
78
79 #include <climits>
80
81 // FIXME: Restrict this as soon the availableUndoSteps has been merged to Qt
82 #if QT_VERSION < 0x040600
83 #define availableUndoSteps revision
84 #endif
85
86 //#define DEBUG_KEY  1
87 #if DEBUG_KEY
88 #   define KEY_DEBUG(s) qDebug() << s
89 #else
90 #   define KEY_DEBUG(s)
91 #endif
92
93 //#define DEBUG_UNDO  1
94 #if DEBUG_UNDO
95 #   define UNDO_DEBUG(s) qDebug() << << m_tc.document()->availableUndoSteps() << s
96 #else
97 #   define UNDO_DEBUG(s)
98 #endif
99
100 using namespace Utils;
101
102 namespace FakeVim {
103 namespace Internal {
104
105 ///////////////////////////////////////////////////////////////////////
106 //
107 // FakeVimHandler
108 //
109 ///////////////////////////////////////////////////////////////////////
110
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
121
122 #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
123
124 const int ParagraphSeparator = 0x00002029;
125
126 using namespace Qt;
127
128
129 enum Mode
130 {
131     InsertMode,
132     CommandMode,
133     ExMode,
134     SearchForwardMode,
135     SearchBackwardMode,
136 };
137
138 enum SubMode
139 {
140     NoSubMode,
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
153 };
154
155 enum SubSubMode
156 {
157     // typically used for things that require one more data item
158     // and are 'nested' behind a mode
159     NoSubSubMode,
160     FtSubSubMode,       // used for f, F, t, T
161     MarkSubSubMode,     // used for m
162     BackTickSubSubMode, // used for `
163     TickSubSubMode,     // used for '
164 };
165
166 enum VisualMode
167 {
168     NoVisualMode,
169     VisualCharMode,
170     VisualLineMode,
171     VisualBlockMode,
172 };
173
174 enum MoveType
175 {
176     MoveExclusive,
177     MoveInclusive,
178     MoveLineWise,
179 };
180
181 enum RangeMode
182 {
183     RangeCharMode,
184     RangeLineMode,
185     RangeBlockMode,
186 };
187
188 enum EventResult
189 {
190     EventHandled,
191     EventUnhandled,
192     EventPassedToCore
193 };
194
195 struct CursorPosition
196 {
197     // for jump history
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
202 };
203
204 struct Register
205 {
206     Register() : rangemode(RangeCharMode) {}
207     Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
208     QString contents;
209     RangeMode rangemode;
210 };
211
212 struct Range
213 {
214     Range()
215         : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
216     {}
217
218     Range(int b, int e, RangeMode m = RangeCharMode)
219         : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
220     {}
221
222     int beginPos;
223     int endPos;
224     RangeMode rangemode;
225 };
226
227 QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
228 {
229     foreach (QTextEdit::ExtraSelection sel, sels)
230         ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
231     return ts;
232 }
233
234 QString quoteUnprintable(const QString &ba)
235 {
236     QString res;
237     for (int i = 0, n = ba.size(); i != n; ++i) {
238         QChar c = ba.at(i);
239         if (c.isPrint())
240             res += c;
241         else
242             res += QString("\\x%1").arg(c.unicode(), 2, 16);
243     }
244     return res;
245 }
246
247 inline QString msgE20MarkNotSet(const QString &text)
248 {
249     return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text);
250 }
251
252 class FakeVimHandler::Private
253 {
254 public:
255     Private(FakeVimHandler *parent, QWidget *widget);
256
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
262
263     void installEventFilter();
264     void setupWidget();
265     void restoreWidget();
266
267     friend class FakeVimHandler;
268     static int shift(int key) { return key + 32; }
269     static int control(int key) { return key + 256; }
270
271     void init();
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);
280
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; }
288
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
293
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); }
305
306     CursorPosition cursorPosition() const
307         { return CursorPosition(position(), firstVisibleLineInDocument()); }
308     void setCursorPosition(const CursorPosition &p)
309         { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }
310
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);
317
318     void moveToFirstNonBlankOnLine();
319     void moveToTargetColumn();
320     void setTargetColumn() {
321         m_targetColumn = leftDist();
322         //qDebug() << "TARGET: " << m_targetColumn;
323     }
324     void moveToNextWord(bool simple);
325     void moveToMatchingParanthesis();
326     void moveToWordBoundary(bool simple, bool forward);
327
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); }
340
341     void handleFfTt(int key);
342
343     // helper function for handleExCommand. return 1 based line index.
344     int readLineCode(QString &cmd);
345     void selectRange(int beginLine, int endLine);
346
347     void enterInsertMode();
348     void enterCommandMode();
349     void enterExMode();
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(); }
361
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);
365
366 public:
367     QTextEdit *m_textedit;
368     QPlainTextEdit *m_plaintextedit;
369     bool m_wasReadOnly; // saves read-only state of document
370
371     FakeVimHandler *q;
372     Mode m_mode;
373     bool m_passing; // let the core see the next event
374     SubMode m_submode;
375     SubSubMode m_subsubmode;
376     int m_subsubdata;
377     QString m_input;
378     QTextCursor m_tc;
379     QTextCursor m_oldTc; // copy from last event to check for external changes
380     int m_anchor;
381     static QHash<int, Register> m_registers;
382     int m_register;
383     QString m_mvcount;
384     QString m_opcount;
385     MoveType m_movetype;
386     RangeMode m_rangemode;
387
388     bool m_fakeEnd;
389
390     bool isSearchMode() const
391         { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
392     int m_gflag;  // whether current command started with 'g'
393
394     QString m_commandBuffer;
395     QString m_currentFileName;
396     QString m_currentMessage;
397
398     bool m_lastSearchForward;
399     QString m_lastInsertion;
400
401     int anchor() const { return m_anchor; }
402     int position() const { return m_tc.position(); }
403
404     void removeSelectedText();
405     void removeText(const Range &range);
406
407     QString selectedText() const { return text(Range(position(), anchor())); }
408     QString text(const Range &range) const;
409
410     void yankSelectedText();
411     void yankText(const Range &range, int toregister = '"');
412
413     void pasteText(bool afterCursor);
414
415     // undo handling
416     void undo();
417     void redo();
418     QMap<int, int> m_undoCursorPosition; // revision -> position
419
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 '.'
426
427     // extra data for ';'
428     QString m_semicolonCount;
429     int m_semicolonType;  // 'f', 'F', 't', 'T'
430     int m_semicolonKey;
431
432     // history for '/'
433     QString lastSearchString() const;
434     static QStringList m_searchHistory;
435     int m_searchHistoryIndex;
436
437     // history for ':'
438     static QStringList m_commandHistory;
439     int m_commandHistoryIndex;
440
441     // visual line mode
442     void enterVisualMode(VisualMode visualMode);
443     void leaveVisualMode();
444     VisualMode m_visualMode;
445
446     // marks as lines
447     QHash<int, int> m_marks;
448     QString m_oldNeedle;
449
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); }
455
456     // for restoring cursor position
457     int m_savedYankPosition;
458     int m_targetColumn;
459
460     int m_cursorWidth;
461
462     // auto-indent
463     void insertAutomaticIndentation(bool goingDown);
464     bool removeAutomaticIndentation(); // true if something removed
465     // number of autoindented characters
466     int m_justAutoIndented;
467     void handleStartOfLine();
468
469     void recordJump();
470     void recordNewUndo();
471     QVector<CursorPosition> m_jumpListUndo;
472     QVector<CursorPosition> m_jumpListRedo;
473
474     QList<QTextEdit::ExtraSelection> m_searchSelections;
475 };
476
477 QStringList FakeVimHandler::Private::m_searchHistory;
478 QStringList FakeVimHandler::Private::m_commandHistory;
479 QHash<int, Register> FakeVimHandler::Private::m_registers;
480
481 FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
482 {
483     q = parent;
484     m_textedit = qobject_cast<QTextEdit *>(widget);
485     m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
486     init();
487 }
488
489 void FakeVimHandler::Private::init()
490 {
491     m_mode = CommandMode;
492     m_submode = NoSubMode;
493     m_subsubmode = NoSubSubMode;
494     m_passing = false;
495     m_fakeEnd = false;
496     m_lastSearchForward = true;
497     m_register = '"';
498     m_gflag = false;
499     m_visualMode = NoVisualMode;
500     m_targetColumn = 0;
501     m_movetype = MoveInclusive;
502     m_anchor = 0;
503     m_savedYankPosition = 0;
504     m_cursorWidth = EDITOR(cursorWidth());
505     m_inReplay = false;
506     m_justAutoIndented = 0;
507     m_rangemode = RangeCharMode;
508 }
509
510 bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
511 {
512     const int key = ev->key();
513     const int mods = ev->modifiers();
514     KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
515
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)
519             return false;
520         return true;
521     }
522
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
526         if (m_passing) {
527             KEY_DEBUG(" PASSING CTRL KEY");
528             // We get called twice on the same key
529             //m_passing = false;
530             return false;
531         }
532         KEY_DEBUG(" NOT PASSING CTRL KEY");
533         //updateMiniBuffer();
534         return true;
535     }
536
537     // Let other shortcuts trigger
538     return false;
539 }
540
541 EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
542 {
543     int key = ev->key();
544     const int um = key; // keep unmodified key around
545     const int mods = ev->modifiers();
546
547     if (key == Key_Shift || key == Key_Alt || key == Key_Control
548             || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
549     {
550         KEY_DEBUG("PLAIN MODIFIER");
551         return EventUnhandled;
552     }
553
554     if (m_passing) {
555         KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
556         //if (key == ',') { // use ',,' to leave, too.
557         //    qDebug() << "FINISHED...";
558         //    return EventHandled;
559         //}
560         m_passing = false;
561         updateMiniBuffer();
562         KEY_DEBUG("   PASS TO CORE");
563         return EventPassedToCore;
564     }
565
566     // Fake "End of line"
567     m_tc = EDITOR(textCursor());
568
569     if (m_tc.position() != m_oldTc.position())
570         setTargetColumn();
571
572     m_tc.setVisualNavigation(true);
573
574     if (m_fakeEnd)
575         moveRight();
576
577     if ((mods & Qt::ControlModifier) != 0) {
578         key += 256;
579         key += 32; // make it lower case
580     } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
581         key += 32;
582     }
583
584     //if (m_mode == InsertMode)
585     //    joinPreviousEditBlock();
586     //else
587     //    beginEditBlock();
588     EventResult result = handleKey(key, um, ev->text());
589     //endEditBlock();
590
591     // We fake vi-style end-of-line behaviour
592     m_fakeEnd = (atEndOfLine() && m_mode == CommandMode
593                  && m_visualMode != VisualBlockMode);
594
595     if (m_fakeEnd)
596         moveLeft();
597
598     m_oldTc = m_tc;
599     EDITOR(setTextCursor(m_tc));
600     return result;
601 }
602
603 void FakeVimHandler::Private::installEventFilter()
604 {
605     EDITOR(installEventFilter(q));
606 }
607
608 void FakeVimHandler::Private::setupWidget()
609 {
610     enterCommandMode();
611     //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
612     if (m_textedit) {
613         m_textedit->setLineWrapMode(QTextEdit::NoWrap);
614     } else if (m_plaintextedit) {
615         m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
616     }
617     m_wasReadOnly = EDITOR(isReadOnly());
618     //EDITOR(setReadOnly(true));
619
620     QTextCursor tc = EDITOR(textCursor());
621     if (tc.hasSelection()) {
622         int pos = tc.position();
623         int anc = tc.anchor();
624         m_marks['<'] = anc;
625         m_marks['>'] = pos;
626         m_anchor = anc;
627         m_visualMode = VisualCharMode;
628         tc.clearSelection();
629         EDITOR(setTextCursor(tc));
630         m_tc = tc; // needed in updateSelection
631         updateSelection();
632     }
633
634     //showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
635     updateMiniBuffer();
636 }
637
638 void FakeVimHandler::Private::restoreWidget()
639 {
640     //showBlackMessage(QString());
641     //updateMiniBuffer();
642     //EDITOR(removeEventFilter(q));
643     EDITOR(setReadOnly(m_wasReadOnly));
644     EDITOR(setCursorWidth(m_cursorWidth));
645     EDITOR(setOverwriteMode(false));
646
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));
659     }
660
661     m_visualMode = NoVisualMode;
662     updateSelection();
663 }
664
665 EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
666     const QString &text)
667 {
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;
678 }
679
680 void FakeVimHandler::Private::moveDown(int n)
681 {
682 #if 0
683     // does not work for "hidden" documents like in the autotests
684     m_tc.movePosition(Down, MoveAnchor, n);
685 #else
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();
693 #endif
694 }
695
696 void FakeVimHandler::Private::moveToEndOfLine()
697 {
698 #if 0
699     // does not work for "hidden" documents like in the autotests
700     m_tc.movePosition(EndOfLine, MoveAnchor);
701 #else
702     const QTextBlock &block = m_tc.block();
703     setPosition(block.position() + block.length() - 1);
704 #endif
705 }
706
707 void FakeVimHandler::Private::moveBehindEndOfLine()
708 {
709     const QTextBlock &block = m_tc.block();
710     int pos = qMin(block.position() + block.length(), lastPositionInDocument());
711     setPosition(pos);
712 }
713
714 void FakeVimHandler::Private::moveToStartOfLine()
715 {
716 #if 0
717     // does not work for "hidden" documents like in the autotests
718     m_tc.movePosition(StartOfLine, MoveAnchor);
719 #else
720     const QTextBlock &block = m_tc.block();
721     setPosition(block.position());
722 #endif
723 }
724
725 void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
726 {
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()));
732         enterExMode();
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;
737         updateMiniBuffer();
738         return;
739     }
740
741     if (m_visualMode != NoVisualMode)
742         m_marks['>'] = m_tc.position();
743
744     if (m_submode == ChangeSubMode) {
745         if (m_movetype == MoveInclusive)
746             moveRight(); // correction
747         if (anchor() >= position())
748            m_anchor++;
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;
754         yankSelectedText();
755         removeSelectedText();
756         enterInsertMode();
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())
763               m_anchor++;
764         }
765         if (!dotCommand.isEmpty())
766             setDotCommand("d" + dotCommand);
767         yankSelectedText();
768         removeSelectedText();
769         m_submode = NoSubMode;
770         if (atEndOfLine())
771             moveLeft();
772         else
773             setTargetColumn();
774     } else if (m_submode == YankSubMode) {
775         yankSelectedText();
776         m_submode = NoSubMode;
777         if (m_register != '"') {
778             setPosition(m_marks[m_register]);
779             moveToStartOfLine();
780         } else {
781             setPosition(m_savedYankPosition);
782         }
783     } else if (m_submode == ReplaceSubMode) {
784         m_submode = NoSubMode;
785     } else if (m_submode == IndentSubMode) {
786         recordJump();
787         indentRegion();
788         m_submode = NoSubMode;
789         updateMiniBuffer();
790     } else if (m_submode == ShiftRightSubMode) {
791         recordJump();
792         shiftRegionRight(1);
793         m_submode = NoSubMode;
794         updateMiniBuffer();
795     } else if (m_submode == ShiftLeftSubMode) {
796         recordJump();
797         shiftRegionLeft(1);
798         m_submode = NoSubMode;
799         updateMiniBuffer();
800     }
801
802     m_movetype = MoveInclusive;
803     m_mvcount.clear();
804     m_opcount.clear();
805     m_gflag = false;
806     m_register = '"';
807     m_tc.clearSelection();
808     m_rangemode = RangeCharMode;
809
810     updateSelection();
811     updateMiniBuffer();
812 }
813
814 void FakeVimHandler::Private::updateSelection()
815 {
816     QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
817     if (m_visualMode != NoVisualMode) {
818         QTextEdit::ExtraSelection sel;
819         sel.cursor = m_tc;
820         sel.format = m_tc.blockCharFormat();
821 #if 0
822         sel.format.setFontWeight(QFont::Bold);
823         sel.format.setFontUnderline(true);
824 #else
825         sel.format.setForeground(Qt::white);
826         sel.format.setBackground(Qt::black);
827 #endif
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);
853                 diffRow = -diffRow;
854             }
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;
860                     sel.cursor = tc;
861                     sel.cursor.movePosition(Right, MoveAnchor, startColumn);
862                     sel.cursor.movePosition(Right, KeepAnchor, len);
863                     selections.append(sel);
864                 }
865                 tc.movePosition(Down, MoveAnchor, 1);
866             }
867         }
868     }
869     //qDebug() << "SELECTION: " << selections;
870     emit q->selectionChanged(selections);
871 }
872
873 void FakeVimHandler::Private::updateMiniBuffer()
874 {
875     QString msg;
876     if (m_passing) {
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 --";
887         }
888     } else if (m_mode == InsertMode) {
889         if (m_submode == ReplaceSubMode)
890             msg = "-- REPLACE --";
891         else
892             msg = "-- INSERT --";
893     } else {
894         if (m_mode == SearchForwardMode)
895             msg += '/';
896         else if (m_mode == SearchBackwardMode)
897             msg += '?';
898         else if (m_mode == ExMode)
899             msg += ':';
900         foreach (QChar c, m_commandBuffer) {
901             if (c.unicode() < 32) {
902                 msg += '^';
903                 msg += QChar(c.unicode() + 64);
904             } else {
905                 msg += c;
906             }
907         }
908         if (!msg.isEmpty() && m_mode != CommandMode)
909             msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
910     }
911
912     emit q->commandBufferChanged(msg);
913
914     int linesInDoc = linesInDocument();
915     int l = cursorLineInDocument();
916     QString status;
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);
921     } else {
922         status = FakeVimHandler::tr("%1All").arg(pos, -10);
923     }
924     emit q->statusDataChanged(status);
925 }
926
927 void FakeVimHandler::Private::showRedMessage(const QString &msg)
928 {
929     //qDebug() << "MSG: " << msg;
930     m_currentMessage = msg;
931     updateMiniBuffer();
932 }
933
934 void FakeVimHandler::Private::showBlackMessage(const QString &msg)
935 {
936     //qDebug() << "MSG: " << msg;
937     m_commandBuffer = msg;
938     updateMiniBuffer();
939 }
940
941 void FakeVimHandler::Private::notImplementedYet()
942 {
943     qDebug() << "Not implemented in FakeVim";
944     showRedMessage(FakeVimHandler::tr("Not implemented in FakeVim"));
945     updateMiniBuffer();
946 }
947
948 EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
949     const QString &text)
950 {
951     EventResult handled = EventHandled;
952
953     if (m_submode == WindowSubMode) {
954         emit q->windowCommandRequested(key);
955         m_submode = NoSubMode;
956     } else if (m_submode == RegisterSubMode) {
957         m_register = key;
958         m_submode = NoSubMode;
959         m_rangemode = RangeLineMode;
960     } else if (m_submode == ChangeSubMode && key == 'c') { // tested
961         moveDown(count() - 1);
962         moveToEndOfLine();
963         moveLeft();
964         setAnchor();
965         moveToStartOfLine();
966         setTargetColumn();
967         moveUp(count() - 1);
968         m_movetype = MoveLineWise;
969         m_lastInsertion.clear();
970         setDotCommand("%1cc", count());
971         finishMovement();
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);
976         yankText(range);
977         removeText(range);
978         setDotCommand("%1dd", count());
979         m_submode = NoSubMode;
980         moveToFirstNonBlankOnLine();
981         setTargetColumn();
982         finishMovement();
983     } else if (m_submode == ShiftLeftSubMode && key == '<') {
984         setAnchor();
985         moveDown(count() - 1);
986         m_movetype = MoveLineWise;
987         setDotCommand("%1<<", count());
988         finishMovement();
989     } else if (m_submode == ShiftRightSubMode && key == '>') {
990         setAnchor();
991         moveDown(count() - 1);
992         m_movetype = MoveLineWise;
993         setDotCommand("%1>>", count());
994         finishMovement();
995     } else if (m_submode == IndentSubMode && key == '=') {
996         setAnchor();
997         moveDown(count() - 1);
998         m_movetype = MoveLineWise;
999         setDotCommand("%1==", count());
1000         finishMovement();
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();
1009             finishMovement();
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());
1014             if (key == '.')
1015                 moveToFirstNonBlankOnLine();
1016             finishMovement();
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());
1021             if (key == '-')
1022                 moveToFirstNonBlankOnLine();
1023             finishMovement();
1024         } else {
1025             qDebug() << "IGNORED Z_MODE " << key << text;
1026         }
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;
1031         if (key == 'Z')
1032             handleCommand("x");
1033         else if (key == 'Q')
1034             handleCommand("q!");
1035     } else if (m_subsubmode == FtSubSubMode) {
1036         m_semicolonType = m_subsubdata;
1037         m_semicolonKey = key;
1038         handleFfTt(key);
1039         m_subsubmode = NoSubSubMode;
1040         finishMovement(QString("%1%2%3")
1041             .arg(count())
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())) {
1047             if (atEndOfLine())
1048                 moveLeft();
1049             setAnchor();
1050             moveRight(count());
1051             removeSelectedText();
1052             m_tc.insertText(QString(count(), text.at(0)));
1053             m_movetype = MoveExclusive;
1054             setDotCommand("%1r" + text, count());
1055         }
1056         setTargetColumn();
1057         m_submode = NoSubMode;
1058         finishMovement();
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();
1068             finishMovement();
1069         } else {
1070             showRedMessage(msgE20MarkNotSet(text));
1071         }
1072         m_subsubmode = NoSubSubMode;
1073     } else if (key >= '0' && key <= '9') {
1074         if (key == '0' && m_mvcount.isEmpty()) {
1075             moveToStartOfLine();
1076             setTargetColumn();
1077             finishMovement();
1078         } else {
1079             m_mvcount.append(QChar(key));
1080         }
1081     } else if (key == '^') {
1082         moveToFirstNonBlankOnLine();
1083         finishMovement();
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;
1091         finishMovement();
1092     } else if (key == ';') {
1093         m_subsubmode = FtSubSubMode;
1094         m_subsubdata = m_semicolonType;
1095         handleFfTt(m_semicolonKey);
1096         m_subsubmode = NoSubSubMode;
1097         finishMovement();
1098     } else if (key == ':') {
1099         enterExMode();
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;
1106         updateMiniBuffer();
1107     } else if (key == '/' || key == '?') {
1108         if (hasConfig(ConfigIncSearch)) {
1109             // re-use the core dialog.
1110             emit q->findRequested(key == '?');
1111         } else {
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;
1120             updateMiniBuffer();
1121         }
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 == '*');
1130         updateMiniBuffer();
1131         search(needle, m_lastSearchForward);
1132         recordJump();
1133     } else if (key == '\'') {
1134         m_subsubmode = TickSubSubMode;
1135     } else if (key == '|') {
1136         moveToStartOfLine();
1137         moveRight(qMin(count(), rightDist()) - 1);
1138         setTargetColumn();
1139         finishMovement();
1140     } else if (key == '!' && m_visualMode == NoVisualMode) {
1141         m_submode = FilterSubMode;
1142     } else if (key == '!' && m_visualMode != NoVisualMode) {
1143         enterExMode();
1144         m_currentMessage.clear();
1145         m_commandBuffer = "'<,'>!";
1146         m_commandHistory.append(QString());
1147         m_commandHistoryIndex = m_commandHistory.size() - 1;
1148         updateMiniBuffer();
1149     } else if (key == '"') {
1150         m_submode = RegisterSubMode;
1151     } else if (unmodified == Key_Return) {
1152         moveToStartOfLine();
1153         moveDown();
1154         moveToFirstNonBlankOnLine();
1155         finishMovement();
1156     } else if (key == '-') {
1157         moveToStartOfLine();
1158         moveUp();
1159         moveToFirstNonBlankOnLine();
1160         finishMovement();
1161     } else if (key == Key_Home) {
1162         moveToStartOfLine();
1163         setTargetColumn();
1164         finishMovement();
1165     } else if (key == '$' || key == Key_End) {
1166         int submode = m_submode;
1167         moveToEndOfLine();
1168         m_movetype = MoveExclusive;
1169         setTargetColumn();
1170         if (submode == NoSubMode)
1171             m_targetColumn = -1;
1172         finishMovement("$");
1173     } else if (key == ',') {
1174         // FIXME: use some other mechanism
1175         //m_passing = true;
1176         m_passing = !m_passing;
1177         updateMiniBuffer();
1178     } else if (key == '.') {
1179         //qDebug() << "REPEATING" << quoteUnprintable(m_dotCommand);
1180         QString savedCommand = m_dotCommand;
1181         m_dotCommand.clear();
1182         replay(savedCommand, count());
1183         enterCommandMode();
1184         m_dotCommand = savedCommand;
1185     } else if (key == '<' && m_visualMode == NoVisualMode) {
1186         m_submode = ShiftLeftSubMode;
1187     } else if (key == '<' && m_visualMode != NoVisualMode) {
1188         shiftRegionLeft(1);
1189         leaveVisualMode();
1190     } else if (key == '>' && m_visualMode == NoVisualMode) {
1191         m_submode = ShiftRightSubMode;
1192     } else if (key == '>' && m_visualMode != NoVisualMode) {
1193         shiftRegionRight(1);
1194         leaveVisualMode();
1195     } else if (key == '=' && m_visualMode == NoVisualMode) {
1196         m_submode = IndentSubMode;
1197     } else if (key == '=' && m_visualMode != NoVisualMode) {
1198         indentRegion();
1199         leaveVisualMode();
1200     } else if (key == '%') {
1201         m_movetype = MoveExclusive;
1202         moveToMatchingParanthesis();
1203         finishMovement();
1204     } else if (key == 'a') {
1205         enterInsertMode();
1206         m_lastInsertion.clear();
1207         if (!atEndOfLine())
1208             moveRight();
1209         updateMiniBuffer();
1210     } else if (key == 'A') {
1211         enterInsertMode();
1212         moveToEndOfLine();
1213         setDotCommand("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);
1220         finishMovement();
1221     } else if (key == 'B') {
1222         m_movetype = MoveExclusive;
1223         moveToWordBoundary(true, false);
1224         finishMovement();
1225     } else if (key == 'c' && m_visualMode == NoVisualMode) {
1226         setAnchor();
1227         m_submode = ChangeSubMode;
1228     } else if (key == 'c' && m_visualMode == VisualCharMode) {
1229         leaveVisualMode();
1230         m_submode = ChangeSubMode;
1231         finishMovement();
1232     } else if (key == 'C') {
1233         setAnchor();
1234         moveToEndOfLine();
1235         yankSelectedText();
1236         removeSelectedText();
1237         enterInsertMode();
1238         setDotCommand("C");
1239         finishMovement();
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();
1245             moveToEndOfLine();
1246             setAnchor();
1247             setPosition(m_savedYankPosition);
1248         } else {
1249             if (atEndOfLine())
1250                 moveLeft();
1251             setAnchor();
1252         }
1253         m_opcount = m_mvcount;
1254         m_mvcount.clear();
1255         m_submode = DeleteSubMode;
1256     } else if ((key == 'd' || key == 'x') && m_visualMode == VisualCharMode) {
1257         leaveVisualMode();
1258         m_submode = DeleteSubMode;
1259         finishMovement();
1260     } else if ((key == 'd' || key == 'x') && m_visualMode == VisualLineMode) {
1261         leaveVisualMode();
1262         m_rangemode = RangeLineMode;
1263         yankSelectedText();
1264         removeSelectedText();
1265     } else if ((key == 'd' || key == 'x') && m_visualMode == VisualBlockMode) {
1266         leaveVisualMode();
1267         m_rangemode = RangeBlockMode;
1268         yankSelectedText();
1269         removeSelectedText();
1270         setPosition(qMin(position(), anchor()));
1271     } else if (key == 'D') {
1272         setAnchor();
1273         m_submode = DeleteSubMode;
1274         moveDown(qMax(count() - 1, 0));
1275         m_movetype = MoveExclusive;
1276         moveToEndOfLine();
1277         setDotCommand("D");
1278         finishMovement();
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);
1285         finishMovement();
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);
1293         finishMovement();
1294     } else if (key == control('e')) {
1295         // FIXME: this should use the "scroll" option, and "count"
1296         if (cursorLineOnScreen() == 0)
1297             moveDown(1);
1298         scrollDown(1);
1299         finishMovement();
1300     } else if (key == 'f') {
1301         m_subsubmode = FtSubSubMode;
1302         m_movetype = MoveInclusive;
1303         m_subsubdata = key;
1304     } else if (key == 'F') {
1305         m_subsubmode = FtSubSubMode;
1306         m_movetype = MoveExclusive;
1307         m_subsubdata = key;
1308     } else if (key == 'g') {
1309         if (m_gflag) {
1310             m_gflag = false;
1311             m_tc.setPosition(firstPositionInLine(1), KeepAnchor);
1312             handleStartOfLine();
1313             finishMovement();
1314         } else {
1315             m_gflag = true;
1316         }
1317     } else if (key == 'G') {
1318         int n = m_mvcount.isEmpty() ? linesInDocument() : count();
1319         m_tc.setPosition(firstPositionInLine(n), KeepAnchor);
1320         handleStartOfLine();
1321         finishMovement();
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)
1326             ++n;
1327         moveLeft(n);
1328         setTargetColumn();
1329         finishMovement("h");
1330     } else if (key == 'H') {
1331         m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
1332         moveDown(qMax(count() - 1, 0));
1333         handleStartOfLine();
1334         finishMovement();
1335     } else if (key == 'i' || key == Key_Insert) {
1336         setDotCommand("i"); // setDotCommand("%1i", count());
1337         enterInsertMode();
1338         updateMiniBuffer();
1339         if (atEndOfLine())
1340             moveLeft();
1341     } else if (key == 'I') {
1342         setDotCommand("I"); // setDotCommand("%1I", count());
1343         enterInsertMode();
1344         if (m_gflag)
1345             moveToStartOfLine();
1346         else
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();
1354         }
1355     } else if (key == 'j' || key == Key_Down) {
1356         if (m_submode == NoSubMode || m_submode == ZSubMode
1357                 || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1358             moveDown(count());
1359         } else {
1360             m_movetype = MoveLineWise;
1361             moveToStartOfLine();
1362             setAnchor();
1363             moveDown(count() + 1);
1364         }
1365         finishMovement("j");
1366     } else if (key == 'J') {
1367         if (m_submode == NoSubMode) {
1368             for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
1369                 moveToEndOfLine();
1370                 setAnchor();
1371                 moveRight();
1372                 while (characterAtCursor() == ' ')
1373                     moveRight();
1374                 removeSelectedText();
1375                 if (!m_gflag)
1376                     m_tc.insertText(" ");
1377             }
1378             if (!m_gflag)
1379                 moveLeft();
1380         }
1381     } else if (key == 'k' || key == Key_Up) {
1382         if (m_submode == NoSubMode || m_submode == ZSubMode
1383                 || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
1384             moveUp(count());
1385         } else {
1386             m_movetype = MoveLineWise;
1387             moveToStartOfLine();
1388             moveDown();
1389             setAnchor();
1390             moveUp(count() + 1);
1391         }
1392         finishMovement("k");
1393     } else if (key == 'l' || key == Key_Right || key == ' ') {
1394         m_movetype = MoveExclusive;
1395         moveRight(qMin(count(), rightDist()));
1396         setTargetColumn();
1397         finishMovement("l");
1398     } else if (key == 'L') {
1399         m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
1400         moveUp(qMax(count(), 1));
1401         handleStartOfLine();
1402         finishMovement();
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();
1410         finishMovement();
1411     } else if (key == 'n') { // FIXME: see comment for '/'
1412         if (hasConfig(ConfigIncSearch))
1413             emit q->findNextRequested(false);
1414         else
1415             search(lastSearchString(), m_lastSearchForward);
1416         recordJump();
1417     } else if (key == 'N') {
1418         if (hasConfig(ConfigIncSearch))
1419             emit q->findNextRequested(true);
1420         else
1421             search(lastSearchString(), !m_lastSearchForward);
1422         recordJump();
1423     } else if (key == 'o' || key == 'O') {
1424         beginEditBlock();
1425         setDotCommand("%1o", count());
1426         enterInsertMode();
1427         moveToFirstNonBlankOnLine();
1428         if (key == 'O')
1429             moveUp();
1430         moveToEndOfLine();
1431         m_tc.insertText("\n");
1432         insertAutomaticIndentation(key == 'o');
1433         endEditBlock();
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();
1439         }
1440     } else if (key == 'p' || key == 'P') {
1441         pasteText(key == 'p');
1442         setTargetColumn();
1443         setDotCommand("%1p", count());
1444         finishMovement();
1445     } else if (key == 'r') {
1446         m_submode = ReplaceSubMode;
1447         setDotCommand("r");
1448     } else if (key == 'R') {
1449         // FIXME: right now we repeat the insertion count() times,
1450         // but not the deletion
1451         m_lastInsertion.clear();
1452         enterInsertMode();
1453         m_submode = ReplaceSubMode;
1454         setDotCommand("R");
1455     } else if (key == control('r')) {
1456         redo();
1457     } else if (key == 's') {
1458         if (atEndOfLine())
1459             moveLeft();
1460         setAnchor();
1461         moveRight(qMin(count(), rightDist()));
1462         yankSelectedText();
1463         removeSelectedText();
1464         setDotCommand("%1s", count());
1465         m_opcount.clear();
1466         m_mvcount.clear();
1467         enterInsertMode();
1468     } else if (key == 't') {
1469         m_movetype = MoveInclusive;
1470         m_subsubmode = FtSubSubMode;
1471         m_subsubdata = key;
1472     } else if (key == 'T') {
1473         m_movetype = MoveExclusive;
1474         m_subsubmode = FtSubSubMode;
1475         m_subsubdata = key;
1476     } else if (key == 'u') {
1477         undo();
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);
1484         finishMovement();
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;
1497         } else {
1498             moveToNextWord(false);
1499             m_movetype = MoveExclusive;
1500         }
1501         finishMovement("w");
1502     } else if (key == 'W') {
1503         if (m_submode == ChangeSubMode) {
1504             moveToWordBoundary(true, true);
1505             m_movetype = MoveInclusive;
1506         } else {
1507             moveToNextWord(true);
1508             m_movetype = MoveExclusive;
1509         }
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;
1515         if (atEndOfLine())
1516             moveLeft();
1517         setAnchor();
1518         m_submode = DeleteSubMode;
1519         moveRight(qMin(count(), rightDist()));
1520         setDotCommand("%1x", count());
1521         finishMovement();
1522     } else if (key == 'X') {
1523         if (leftDist() > 0) {
1524             setAnchor();
1525             moveLeft(qMin(count(), leftDist()));
1526             yankSelectedText();
1527             removeSelectedText();
1528         }
1529         finishMovement();
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));
1536         if (count() > 1)
1537             showBlackMessage(QString("%1 lines yanked").arg(count()));
1538         m_rangemode = RangeLineMode;
1539         m_movetype = MoveLineWise;
1540         m_submode = YankSubMode;
1541         finishMovement();
1542     } else if (key == 'y' && m_visualMode == NoVisualMode) {
1543         if (m_rangemode == RangeLineMode) {
1544             m_savedYankPosition = position();
1545             setAnchor(firstPositionInLine(cursorLineInDocument() + 1));
1546         } else {
1547             m_savedYankPosition = position();
1548             if (atEndOfLine())
1549                 moveLeft();
1550             setAnchor();
1551             m_rangemode = RangeCharMode;
1552         }
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()));
1559         leaveVisualMode();
1560         finishMovement();
1561     } else if ((key == 'y' && m_visualMode == VisualLineMode)
1562             || (key == 'Y' && m_visualMode == VisualLineMode)
1563             || (key == 'Y' && m_visualMode == VisualCharMode)) {
1564         m_rangemode = RangeLineMode;
1565         yankSelectedText();
1566         setPosition(qMin(position(), anchor()));
1567         moveToStartOfLine();
1568         leaveVisualMode();
1569         finishMovement();
1570     } else if ((key == 'y' || key == 'Y') && m_visualMode == VisualBlockMode) {
1571         m_rangemode = RangeBlockMode;
1572         yankSelectedText();
1573         setPosition(qMin(position(), anchor()));
1574         leaveVisualMode();
1575         finishMovement();
1576     } else if (key == 'z') {
1577         m_submode = ZSubMode;
1578     } else if (key == 'Z') {
1579         m_submode = CapitalZSubMode;
1580     } else if (key == '~' && !atEndOfLine()) {
1581         beginEditBlock();
1582         setAnchor();
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();
1589         }
1590         m_tc.insertText(str);
1591         endEditBlock();
1592     } else if (key == Key_PageDown || key == control('f')) {
1593         moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
1594         scrollToLineInDocument(cursorLineInDocument());
1595         handleStartOfLine();
1596         finishMovement();
1597     } else if (key == Key_PageUp || key == control('b')) {
1598         moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
1599         scrollToLineInDocument(cursorLineInDocument() + linesOnScreen() - 2);
1600         handleStartOfLine();
1601         finishMovement();
1602     } else if (key == Key_Delete) {
1603         setAnchor();
1604         moveRight(qMin(1, rightDist()));
1605         removeSelectedText();
1606     } else if (key == Key_Escape) {
1607         if (m_visualMode != NoVisualMode) {
1608             leaveVisualMode();
1609         } else if (m_submode != NoSubMode) {
1610             m_submode = NoSubMode;
1611             m_subsubmode = NoSubSubMode;
1612             finishMovement();
1613         }
1614     } else {
1615         qDebug() << "IGNORED IN COMMAND MODE: " << key << text
1616             << " VISUAL: " << m_visualMode;
1617         handled = EventUnhandled;
1618     }
1619
1620     return handled;
1621 }
1622
1623 EventResult FakeVimHandler::Private::handleInsertMode(int key, int,
1624     const QString &text)
1625 {
1626     if (key == Key_Escape || key == 27 || key == control('c')) {
1627         // start with '1', as one instance was already physically inserted
1628         // while typing
1629         QString data = m_lastInsertion;
1630         for (int i = 1; i < count(); ++i) {
1631             m_tc.insertText(m_lastInsertion);
1632             data += m_lastInsertion;
1633         }
1634         moveLeft(qMin(1, leftDist()));
1635         setTargetColumn();
1636         m_dotCommand += m_lastInsertion;
1637         m_dotCommand += QChar(27);
1638         recordNewUndo();
1639         enterCommandMode();
1640     } else if (key == Key_Insert) {
1641         if (m_submode == ReplaceSubMode) {
1642             EDITOR(setCursorWidth(m_cursorWidth));
1643             EDITOR(setOverwriteMode(false));
1644             m_submode = NoSubMode;
1645         } else {
1646             EDITOR(setCursorWidth(m_cursorWidth));
1647             EDITOR(setOverwriteMode(true));
1648             m_submode = ReplaceSubMode;
1649         }
1650     } else if (key == Key_Left) {
1651         moveLeft(count());
1652         setTargetColumn();
1653         m_lastInsertion.clear();
1654     } else if (key == Key_Down) {
1655         //removeAutomaticIndentation();
1656         m_submode = NoSubMode;
1657         moveDown(count());
1658         m_lastInsertion.clear();
1659     } else if (key == Key_Up) {
1660         //removeAutomaticIndentation();
1661         m_submode = NoSubMode;
1662         moveUp(count());
1663         m_lastInsertion.clear();
1664     } else if (key == Key_Right) {
1665         moveRight(count());
1666         setTargetColumn();
1667         m_lastInsertion.clear();
1668     } else if (key == Key_Return) {
1669         m_submode = NoSubMode;
1670         m_tc.insertBlock();
1671         m_lastInsertion += "\n";
1672         insertAutomaticIndentation(true);
1673         setTargetColumn();
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);
1679                 setTargetColumn();
1680             }
1681     } else if (key == Key_Delete) {
1682         m_tc.deleteChar();
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);
1696         setTargetColumn();
1697     } else if (key >= control('a') && key <= control('z')) {
1698         // ignore these
1699     } else if (!text.isEmpty()) {
1700         m_justAutoIndented = false;
1701         m_lastInsertion.append(text);
1702         if (m_submode == ReplaceSubMode) {
1703             if (atEndOfLine())
1704                 m_submode = NoSubMode;
1705             else
1706                 m_tc.deleteChar();
1707         }
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));
1714         }
1715
1716         if (!m_inReplay)
1717             emit q->completionRequested();
1718         setTargetColumn();
1719     } else {
1720         return EventUnhandled;
1721     }
1722     updateMiniBuffer();
1723     return EventHandled;
1724 }
1725
1726 EventResult FakeVimHandler::Private::handleMiniBufferModes(int key, int unmodified,
1727     const QString &text)
1728 {
1729     Q_UNUSED(text)
1730
1731     if (key == Key_Escape || key == control('c')) {
1732         m_commandBuffer.clear();
1733         enterCommandMode();
1734         updateMiniBuffer();
1735     } else if (key == Key_Backspace) {
1736         if (m_commandBuffer.isEmpty()) {
1737             enterCommandMode();
1738         } else {
1739             m_commandBuffer.chop(1);
1740         }
1741         updateMiniBuffer();
1742     } else if (key == Key_Left) {
1743         // FIXME:
1744         if (!m_commandBuffer.isEmpty())
1745             m_commandBuffer.chop(1);
1746         updateMiniBuffer();
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);
1752             leaveVisualMode();
1753         }
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);
1760             recordJump();
1761         }
1762         enterCommandMode();
1763         updateMiniBuffer();
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));
1770         }
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));
1775         }
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));
1780         }
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));
1785         }
1786     } else if (key == Key_Tab) {
1787         m_commandBuffer += QChar(9);
1788         updateMiniBuffer();
1789     } else if (QChar(key).isPrint()) {
1790         m_commandBuffer += QChar(key);
1791         updateMiniBuffer();
1792     } else {
1793         qDebug() << "IGNORED IN MINIBUFFER MODE: " << key << text;
1794         return EventUnhandled;
1795     }
1796     return EventHandled;
1797 }
1798
1799 // 1 based.
1800 int FakeVimHandler::Private::readLineCode(QString &cmd)
1801 {
1802     //qDebug() << "CMD: " << cmd;
1803     if (cmd.isEmpty())
1804         return -1;
1805     QChar c = cmd.at(0);
1806     cmd = cmd.mid(1);
1807     if (c == '.')
1808         return cursorLineInDocument() + 1;
1809     if (c == '$')
1810         return linesInDocument();
1811     if (c == '\'' && !cmd.isEmpty()) {
1812         int mark = m_marks.value(cmd.at(0).unicode());
1813         if (!mark) {
1814             showRedMessage(msgE20MarkNotSet(cmd.at(0)));
1815             cmd = cmd.mid(1);
1816             return -1;
1817         }
1818         cmd = cmd.mid(1);
1819         return lineForPosition(mark);
1820     }
1821     if (c == '-') {
1822         int n = readLineCode(cmd);
1823         return cursorLineInDocument() + 1 - (n == -1 ? 1 : n);
1824     }
1825     if (c == '+') {
1826         int n = readLineCode(cmd);
1827         return cursorLineInDocument() + 1 + (n == -1 ? 1 : n);
1828     }
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);
1832         if (pos == -1) {
1833             showRedMessage(msgE20MarkNotSet(cmd.at(0)));
1834             cmd = cmd.mid(1);
1835             return -1;
1836         }
1837         cmd = cmd.mid(1);
1838         return lineForPosition(pos);
1839     }
1840     if (c.isDigit()) {
1841         int n = c.unicode() - '0';
1842         while (!cmd.isEmpty()) {
1843             c = cmd.at(0);
1844             if (!c.isDigit())
1845                 break;
1846             cmd = cmd.mid(1);
1847             n = n * 10 + (c.unicode() - '0');
1848         }
1849         //qDebug() << "N: " << n;
1850         return n;
1851     }
1852     // not parsed
1853     cmd = c + cmd;
1854     return -1;
1855 }
1856
1857 void FakeVimHandler::Private::selectRange(int beginLine, int endLine)
1858 {
1859     if (beginLine == -1)
1860         beginLine = cursorLineInDocument();
1861     if (endLine == -1)
1862         endLine = cursorLineInDocument();
1863     if (beginLine > endLine)
1864         qSwap(beginLine, endLine);
1865     setAnchor(firstPositionInLine(beginLine));
1866     if (endLine == linesInDocument())
1867        setPosition(lastPositionInLine(endLine));
1868     else
1869        setPosition(firstPositionInLine(endLine + 1));
1870 }
1871
1872 void FakeVimHandler::Private::handleCommand(const QString &cmd)
1873 {
1874     m_tc = EDITOR(textCursor());
1875     handleExCommand(cmd);
1876     EDITOR(setTextCursor(m_tc));
1877 }
1878
1879 void FakeVimHandler::Private::handleExCommand(const QString &cmd0)
1880 {
1881     QString cmd = cmd0;
1882     if (cmd.startsWith(QLatin1Char('%')))
1883         cmd = "1,$" + cmd.mid(1);
1884
1885     int beginLine = -1;
1886     int endLine = -1;
1887
1888     int line = readLineCode(cmd);
1889     if (line != -1)
1890         beginLine = line;
1891
1892     if (cmd.startsWith(',')) {
1893         cmd = cmd.mid(1);
1894         line = readLineCode(cmd);
1895         if (line != -1)
1896             endLine = line;
1897     }
1898
1899     //qDebug() << "RANGE: " << beginLine << endLine << cmd << cmd0 << m_marks;
1900
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]*)");
1908
1909     enterCommandMode();
1910     showBlackMessage(QString());
1911
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()];
1922             r.contents = text;
1923             r.rangemode = RangeLineMode;
1924         }
1925     } else if (reWrite.indexIn(cmd) != -1) { // :w and :x
1926         bool noArgs = (beginLine == -1);
1927         if (beginLine == -1)
1928             beginLine = 0;
1929         if (endLine == -1)
1930             endLine = linesInDocument();
1931         //qDebug() << "LINES: " << beginLine << endLine;
1932         int indexOfSpace = cmd.indexOf(QChar(' '));
1933         QString prefix;
1934         if (indexOfSpace < 0)
1935             prefix = cmd;
1936         else
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)) {
1949             file1.close();
1950             QTextCursor tc = m_tc;
1951             Range range(firstPositionInLine(beginLine),
1952                 firstPositionInLine(endLine), RangeLineMode);
1953             QString contents = text(range);
1954             m_tc = tc;
1955             qDebug() << "LINES: " << beginLine << endLine;
1956             bool handled = false;
1957             emit q->writeFileRequested(&handled, fileName, contents);
1958             // nobody cared, so act ourselves
1959             if (!handled) {
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);
1965                     ts << contents;
1966                 } else {
1967                     showRedMessage(FakeVimHandler::tr("Cannot open file '%1' for writing").arg(fileName));
1968                 }
1969             }
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()));
1977             if (quitAll)
1978                 passUnknownExCommand(forced ? "qa!" : "qa");
1979             else if (quit)
1980                 passUnknownExCommand(forced ? "q!" : "q");
1981         } else {
1982             showRedMessage(FakeVimHandler::tr("Cannot open file '%1' for reading").arg(fileName));
1983         }
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();
1998         QProcess proc;
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);
2006         leaveVisualMode();
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);
2014         leaveVisualMode();
2015         showBlackMessage(FakeVimHandler::tr("%n lines >ed %1 time", 0, (endLine - beginLine + 1)).arg(1));
2016     } else if (cmd == "red" || cmd == "redo") { // :redo
2017         redo();
2018         updateMiniBuffer();
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');
2035         beginEditBlock();
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)
2042                     break;
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";
2050                     } else {
2051                         text.replace(ParagraphSeparator, "\n");
2052                         text.replace(pattern, replacement);
2053                     }
2054                     m_tc.removeSelectedText();
2055                     m_tc.insertText(text);
2056                 }
2057                 if (!global)
2058                     break;
2059             }
2060         }
2061         endEditBlock();
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)
2074                 {} // nothing to do
2075         } else if (act) {
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)
2085                 {} // nothing to do
2086         } else if (arg.contains('=')) {
2087             // non-boolean config to set
2088             int p = arg.indexOf('=');
2089             act = theFakeVimSettings()->item(arg.left(p));
2090             if (act)
2091                 act->setValue(arg.mid(p + 1));
2092         } else {
2093             showRedMessage(FakeVimHandler::tr("E512: Unknown option: ") + arg);
2094         }
2095         updateMiniBuffer();
2096     } else if (reHistory.indexIn(cmd) != -1) { // :history
2097         QString arg = reSet.cap(3);
2098         if (arg.isEmpty()) {
2099             QString info;
2100             info += "#  command history\n";
2101             int i = 0;
2102             foreach (const QString &item, m_commandHistory) {
2103                 ++i;
2104                 info += QString("%1 %2\n").arg(i, -8).arg(item);
2105             }
2106             emit q->extraInformationChanged(info);
2107         } else {
2108             notImplementedYet();
2109         }
2110         updateMiniBuffer();
2111     } else {
2112         passUnknownExCommand(cmd);
2113     }
2114 }
2115
2116 void FakeVimHandler::Private::passUnknownExCommand(const QString &cmd)
2117 {
2118     emit q->handleExCommandRequested(cmd);
2119 }
2120
2121 static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *flags)
2122 {
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;
2129 }
2130
2131 void FakeVimHandler::Private::search(const QString &needle0, bool forward)
2132 {
2133     showBlackMessage((forward ? '/' : '?') + needle0);
2134     CursorPosition origPosition = cursorPosition();
2135     QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2136     if (!forward)
2137         flags |= QTextDocument::FindBackward;
2138
2139     QString needle = needle0;
2140     vimPatternToQtPattern(&needle, &flags);
2141
2142     if (forward)
2143         m_tc.movePosition(Right, MoveAnchor, 1);
2144
2145     int oldLine = cursorLineInDocument() - cursorLineOnScreen();
2146
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);
2155     } else {
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);
2163             if (forward)
2164                 showRedMessage(FakeVimHandler::tr("search hit BOTTOM, continuing at TOP"));
2165             else
2166                 showRedMessage(FakeVimHandler::tr("search hit TOP, continuing at BOTTOM"));
2167             highlightMatches(needle);
2168         } else {
2169             highlightMatches(QString());
2170             setCursorPosition(origPosition);
2171             showRedMessage(FakeVimHandler::tr("Pattern not found: ") + needle);
2172         }
2173     }
2174 }
2175
2176 void FakeVimHandler::Private::highlightMatches(const QString &needle0)
2177 {
2178     if (!hasConfig(ConfigHlSearch))
2179         return;
2180     if (needle0 == m_oldNeedle)
2181         return;
2182     m_oldNeedle = needle0;
2183     m_searchSelections.clear();
2184
2185     if (!needle0.isEmpty()) {
2186         QTextCursor tc = m_tc;
2187         tc.movePosition(StartOfDocument, MoveAnchor);
2188
2189         QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
2190         QString needle = needle0;
2191         vimPatternToQtPattern(&needle, &flags);
2192
2193
2194         EDITOR(setTextCursor(tc));
2195         while (EDITOR(find(needle, flags))) {
2196             tc = EDITOR(textCursor());
2197             QTextEdit::ExtraSelection sel;
2198             sel.cursor = tc;
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));
2204         }
2205     }
2206     updateSelection();
2207 }
2208
2209 void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
2210 {
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()) {
2216             setPosition(i);
2217             return;
2218         }
2219     }
2220     setPosition(block.position());
2221 }
2222
2223 void FakeVimHandler::Private::indentRegion(QChar typedChar)
2224 {
2225     //int savedPos = anchor();
2226     int beginLine = lineForPosition(anchor());
2227     int endLine = lineForPosition(position());
2228     if (beginLine > endLine)
2229         qSwap(beginLine, endLine);
2230
2231     int amount = 0;
2232     emit q->indentRegion(&amount, beginLine, endLine, typedChar);
2233
2234     setPosition(firstPositionInLine(beginLine));
2235     moveToFirstNonBlankOnLine();
2236     setTargetColumn();
2237     setDotCommand("%1==", endLine - beginLine + 1);
2238 }
2239
2240 void FakeVimHandler::Private::shiftRegionRight(int repeat)
2241 {
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);
2249
2250     for (int line = beginLine; line <= endLine; ++line) {
2251         setPosition(firstPositionInLine(line));
2252         m_tc.insertText(indent);
2253     }
2254
2255     setPosition(firstPos);
2256     moveToFirstNonBlankOnLine();
2257     setTargetColumn();
2258     setDotCommand("%1>>", endLine - beginLine + 1);
2259 }
2260
2261 void FakeVimHandler::Private::shiftRegionLeft(int repeat)
2262 {
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);
2270
2271     for (int line = beginLine; line <= endLine; ++line) {
2272         int pos = firstPositionInLine(line);
2273         setPosition(pos);
2274         setAnchor(pos);
2275         QString text = m_tc.block().text();
2276         int amount = 0;
2277         int i = 0;
2278         for (; i < text.size() && amount <= shift; ++i) {
2279             if (text.at(i) == ' ')
2280                 amount++;
2281             else if (text.at(i) == '\t')
2282                 amount += tab; // FIXME: take position into consideration
2283             else
2284                 break;
2285         }
2286         setPosition(pos + i);
2287         text = selectedText();
2288         removeSelectedText();
2289         setPosition(pos);
2290     }
2291
2292     setPosition(firstPos);
2293     moveToFirstNonBlankOnLine();
2294     setTargetColumn();
2295     setDotCommand("%1<<", endLine - beginLine + 1);
2296 }
2297
2298 void FakeVimHandler::Private::moveToTargetColumn()
2299 {
2300     const QTextBlock &block = m_tc.block();
2301     int col = m_tc.position() - block.position();
2302     if (col == m_targetColumn)
2303         return;
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);
2307     else
2308         m_tc.setPosition(block.position() + m_targetColumn, MoveAnchor);
2309 }
2310
2311 /* if simple is given:
2312  *  class 0: spaces
2313  *  class 1: non-spaces
2314  * else
2315  *  class 0: spaces
2316  *  class 1: non-space-or-letter-or-number
2317  *  class 2: letter-or-number
2318  */
2319 static int charClass(QChar c, bool simple)
2320 {
2321     if (simple)
2322         return c.isSpace() ? 0 : 1;
2323     if (c.isLetterOrNumber() || c.unicode() == '_')
2324         return 2;
2325     return c.isSpace() ? 0 : 1;
2326 }
2327
2328 void FakeVimHandler::Private::moveToWordBoundary(bool simple, bool forward)
2329 {
2330     int repeat = count();
2331     QTextDocument *doc = m_tc.document();
2332     int n = forward ? lastPositionInDocument() : 0;
2333     int lastClass = -1;
2334     while (true) {
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)
2339             --repeat;
2340         if (repeat == -1)
2341             break;
2342         lastClass = thisClass;
2343         if (m_tc.position() == n)
2344             break;
2345         forward ? moveRight() : moveLeft();
2346     }
2347     setTargetColumn();
2348 }
2349
2350 void FakeVimHandler::Private::handleFfTt(int key)
2351 {
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();
2358     if (forward)
2359         n += block.length();
2360     int pos = m_tc.position();
2361     while (true) {
2362         pos += forward ? 1 : -1;
2363         if (pos == n)
2364             break;
2365         int uc = doc->characterAt(pos).unicode();
2366         if (uc == ParagraphSeparator)
2367             break;
2368         if (uc == key)
2369             --repeat;
2370         if (repeat == 0) {
2371             if (m_subsubdata == 't')
2372                 --pos;
2373             else if (m_subsubdata == 'T')
2374                 ++pos;
2375
2376             if (forward)
2377                 m_tc.movePosition(Right, KeepAnchor, pos - m_tc.position());
2378             else
2379                 m_tc.movePosition(Left, KeepAnchor, m_tc.position() - pos);
2380             break;
2381         }
2382     }
2383     setTargetColumn();
2384 }
2385
2386 void FakeVimHandler::Private::moveToNextWord(bool simple)
2387 {
2388     // FIXME: 'w' should stop on empty lines, too
2389     int repeat = count();
2390     int n = lastPositionInDocument();
2391     int lastClass = charClass(characterAtCursor(), simple);
2392     while (true) {
2393         QChar c = characterAtCursor();
2394         int thisClass = charClass(c, simple);
2395         if (thisClass != lastClass && thisClass != 0)
2396             --repeat;
2397         if (repeat == 0)
2398             break;
2399         lastClass = thisClass;
2400         moveRight();
2401         if (m_tc.position() == n)
2402             break;
2403     }
2404     setTargetColumn();
2405 }
2406
2407 void FakeVimHandler::Private::moveToMatchingParanthesis()
2408 {
2409     bool moved = false;
2410     bool forward = false;
2411
2412     emit q->moveToMatchingParenthesis(&moved, &forward, &m_tc);
2413
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);
2417     }
2418     setTargetColumn();
2419 }
2420
2421 int FakeVimHandler::Private::cursorLineOnScreen() const
2422 {
2423     if (!editor())
2424         return 0;
2425     QRect rect = EDITOR(cursorRect());
2426     return rect.y() / rect.height();
2427 }
2428
2429 int FakeVimHandler::Private::linesOnScreen() const
2430 {
2431     if (!editor())
2432         return 1;
2433     QRect rect = EDITOR(cursorRect());
2434     return EDITOR(height()) / rect.height();
2435 }
2436
2437 int FakeVimHandler::Private::columnsOnScreen() const
2438 {
2439     if (!editor())
2440         return 1;
2441     QRect rect = EDITOR(cursorRect());
2442     // qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
2443     return EDITOR(width()) / rect.width();
2444 }
2445
2446 int FakeVimHandler::Private::cursorLineInDocument() const
2447 {
2448     return m_tc.block().blockNumber();
2449 }
2450
2451 int FakeVimHandler::Private::cursorColumnInDocument() const
2452 {
2453     return m_tc.position() - m_tc.block().position();
2454 }
2455
2456 int FakeVimHandler::Private::linesInDocument() const
2457 {
2458     return m_tc.isNull() ? 0 : m_tc.document()->blockCount();
2459 }
2460
2461 void FakeVimHandler::Private::scrollToLineInDocument(int line)
2462 {
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, /**/);
2468 }
2469
2470 int FakeVimHandler::Private::firstVisibleLineInDocument() const
2471 {
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();
2477     }
2478     //return scrollBar->value();
2479     return cursorLineInDocument() - cursorLineOnScreen();
2480 }
2481
2482 void FakeVimHandler::Private::scrollUp(int count)
2483 {
2484     scrollToLineInDocument(cursorLineInDocument() - cursorLineOnScreen() - count);
2485 }
2486
2487 int FakeVimHandler::Private::lastPositionInDocument() const
2488 {
2489     QTextBlock block = m_tc.document()->lastBlock();
2490     return block.position() + block.length() - 1;
2491 }
2492
2493 QString FakeVimHandler::Private::lastSearchString() const
2494 {
2495      return m_searchHistory.empty() ? QString() : m_searchHistory.back();
2496 }
2497
2498 QString FakeVimHandler::Private::text(const Range &range) const
2499 {
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();
2505     }
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();
2511     }
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;
2523     }
2524     int len = endColumn - beginColumn + 1;
2525     QString contents;
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(' '));
2533         }
2534         contents += line;
2535         if (!contents.endsWith('\n'))
2536             contents += '\n';
2537         block = block.next();
2538     }
2539     //qDebug() << "SELECTED: " << contents;
2540     return contents;
2541 }
2542
2543 void FakeVimHandler::Private::yankSelectedText()
2544 {
2545     Range range(anchor(), position());
2546     range.rangemode = m_rangemode;
2547     yankText(range, m_register);
2548 }
2549
2550 void FakeVimHandler::Private::yankText(const Range &range, int toregister)
2551 {
2552     Register &reg = m_registers[toregister];
2553     reg.contents = text(range);
2554     reg.rangemode = range.rangemode;
2555     //qDebug() << "YANKED: " << reg.contents;
2556 }
2557
2558 void FakeVimHandler::Private::removeSelectedText()
2559 {
2560     Range range(anchor(), position());
2561     range.rangemode = m_rangemode;
2562     removeText(range);
2563 }
2564
2565 void FakeVimHandler::Private::removeText(const Range &range)
2566 {
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();
2574             return;
2575         }
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();
2584             return;
2585         }
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;
2594
2595             QTextBlock block = m_tc.document()->findBlockByNumber(endLine - 1);
2596             beginEditBlock();
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();
2605             }
2606             endEditBlock();
2607         }
2608     }
2609 }
2610
2611 void FakeVimHandler::Private::pasteText(bool afterCursor)
2612 {
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: {
2617             m_targetColumn = 0;
2618             for (int i = count(); --i >= 0; ) {
2619                 if (afterCursor && rightDist() > 0)
2620                     moveRight();
2621                 fixMarks(position(), text.length());
2622                 m_tc.insertText(text);
2623                 moveLeft();
2624             }
2625             break;
2626         }
2627         case RangeLineMode: {
2628             moveToStartOfLine();
2629             m_targetColumn = 0;
2630             for (int i = count(); --i >= 0; ) {
2631                 if (afterCursor)
2632                     moveDown();
2633                 fixMarks(position(), text.length());
2634                 m_tc.insertText(text);
2635                 moveUp(lines.size() - 1);
2636             }
2637             moveToFirstNonBlankOnLine();
2638             break;
2639         }
2640         case RangeBlockMode: {
2641             beginEditBlock();
2642             QTextBlock block = m_tc.block();
2643             moveRight();
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(' ')));
2654                 } else {
2655                     tc.movePosition(Right, MoveAnchor, col);
2656                 }
2657                 qDebug() << "INSERT " << line << " AT " << tc.position()
2658                     << "COL: " << col;
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);
2667                 }
2668                 block = block.next();
2669             }
2670             endEditBlock();
2671             break;
2672         }
2673     }
2674 }
2675
2676 //FIXME: This needs to called after undo/insert
2677 void FakeVimHandler::Private::fixMarks(int positionAction, int positionChange)
2678 {
2679     QHashIterator<int, int> i(m_marks);
2680     while (i.hasNext()) {
2681         i.next();
2682         if (i.value() >= positionAction) {
2683             if (i.value() + positionChange > 0)
2684                 m_marks[i.key()] = i.value() + positionChange;
2685             else
2686                 m_marks.remove(i.key());
2687         }
2688     }
2689 }
2690
2691 int FakeVimHandler::Private::firstPositionInLine(int line) const
2692 {
2693     return m_tc.document()->findBlockByNumber(line - 1).position();
2694 }
2695
2696 int FakeVimHandler::Private::lastPositionInLine(int line) const
2697 {
2698     QTextBlock block = m_tc.document()->findBlockByNumber(line - 1);
2699     return block.position() + block.length() - 1;
2700 }
2701
2702 int FakeVimHandler::Private::lineForPosition(int pos) const
2703 {
2704     QTextCursor tc = m_tc;
2705     tc.setPosition(pos);
2706     return tc.block().blockNumber() + 1;
2707 }
2708
2709 void FakeVimHandler::Private::enterVisualMode(VisualMode visualMode)
2710 {
2711     setAnchor();
2712     m_visualMode = visualMode;
2713     m_marks['<'] = m_tc.position();
2714     m_marks['>'] = m_tc.position();
2715     updateMiniBuffer();
2716     updateSelection();
2717 }
2718
2719 void FakeVimHandler::Private::leaveVisualMode()
2720 {
2721     m_visualMode = NoVisualMode;
2722     updateMiniBuffer();
2723     updateSelection();
2724 }
2725
2726 QWidget *FakeVimHandler::Private::editor() const
2727 {
2728     return m_textedit
2729         ? static_cast<QWidget *>(m_textedit)
2730         : static_cast<QWidget *>(m_plaintextedit);
2731 }
2732
2733 void FakeVimHandler::Private::undo()
2734 {
2735     int current = m_tc.document()->availableUndoSteps();
2736     //endEditBlock();
2737     EDITOR(undo());
2738     //beginEditBlock();
2739     int rev = m_tc.document()->availableUndoSteps();
2740     if (current == rev)
2741         showBlackMessage(FakeVimHandler::tr("Already at oldest change"));
2742     else
2743         showBlackMessage(QString());
2744
2745     if (m_undoCursorPosition.contains(rev))
2746         m_tc.setPosition(m_undoCursorPosition[rev]);
2747 }
2748
2749 void FakeVimHandler::Private::redo()
2750 {
2751     int current = m_tc.document()->availableUndoSteps();
2752     //endEditBlock();
2753     EDITOR(redo());
2754     //beginEditBlock();
2755     int rev = m_tc.document()->availableUndoSteps();
2756     if (rev == current)
2757         showBlackMessage(FakeVimHandler::tr("Already at newest change"));
2758     else
2759         showBlackMessage(QString());
2760
2761     if (m_undoCursorPosition.contains(rev))
2762         m_tc.setPosition(m_undoCursorPosition[rev]);
2763 }
2764
2765 void FakeVimHandler::Private::enterInsertMode()
2766 {
2767     EDITOR(setCursorWidth(m_cursorWidth));
2768     EDITOR(setOverwriteMode(false));
2769     m_mode = InsertMode;
2770     m_lastInsertion.clear();
2771 }
2772
2773 void FakeVimHandler::Private::enterCommandMode()
2774 {
2775     EDITOR(setCursorWidth(m_cursorWidth));
2776     EDITOR(setOverwriteMode(true));
2777     m_mode = CommandMode;
2778 }
2779
2780 void FakeVimHandler::Private::enterExMode()
2781 {
2782     EDITOR(setCursorWidth(0));
2783     EDITOR(setOverwriteMode(false));
2784     m_mode = ExMode;
2785 }
2786
2787 void FakeVimHandler::Private::recordJump()
2788 {
2789     m_jumpListUndo.append(cursorPosition());
2790     m_jumpListRedo.clear();
2791     UNDO_DEBUG("jumps: " << m_jumpListUndo);
2792 }
2793
2794 void FakeVimHandler::Private::recordNewUndo()
2795 {
2796     //endEditBlock();
2797     UNDO_DEBUG("---- BREAK ----");
2798     //beginEditBlock();
2799 }
2800
2801 void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
2802 {
2803     if (!hasConfig(ConfigAutoIndent))
2804         return;
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())
2809         ++pos;
2810     text.truncate(pos);
2811     // FIXME: handle 'smartindent' and 'cindent'
2812     m_tc.insertText(text);
2813     m_justAutoIndented = text.size();
2814 }
2815
2816 bool FakeVimHandler::Private::removeAutomaticIndentation()
2817 {
2818     if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
2819         return false;
2820     m_tc.movePosition(StartOfLine, KeepAnchor);
2821     m_tc.removeSelectedText();
2822     m_lastInsertion.chop(m_justAutoIndented);
2823     m_justAutoIndented = 0;
2824     return true;
2825 }
2826
2827 void FakeVimHandler::Private::handleStartOfLine()
2828 {
2829     if (hasConfig(ConfigStartOfLine))
2830         moveToFirstNonBlankOnLine();
2831 }
2832
2833 void FakeVimHandler::Private::replay(const QString &command, int n)
2834 {
2835     //qDebug() << "REPLAY: " << command;
2836     m_inReplay = true;
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));
2841         }
2842     }
2843     m_inReplay = false;
2844 }
2845
2846 ///////////////////////////////////////////////////////////////////////
2847 //
2848 // FakeVimHandler
2849 //
2850 ///////////////////////////////////////////////////////////////////////
2851
2852 FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
2853     : QObject(parent), d(new Private(this, widget))
2854 {}
2855
2856 FakeVimHandler::~FakeVimHandler()
2857 {
2858     delete d;
2859 }
2860
2861 bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
2862 {
2863     bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
2864
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;
2872         //return true;
2873         return res == EventHandled;
2874     }
2875
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"
2881             return true;
2882         }
2883         KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
2884         return true;
2885     }
2886
2887     return QObject::eventFilter(ob, ev);
2888 }
2889
2890 void FakeVimHandler::installEventFilter()
2891 {
2892     d->installEventFilter();
2893 }
2894
2895 void FakeVimHandler::setupWidget()
2896 {
2897     d->setupWidget();
2898 }
2899
2900 void FakeVimHandler::restoreWidget()
2901 {
2902     d->restoreWidget();
2903 }
2904
2905 void FakeVimHandler::handleCommand(const QString &cmd)
2906 {
2907     d->handleCommand(cmd);
2908 }
2909
2910 void FakeVimHandler::setCurrentFileName(const QString &fileName)
2911 {
2912    d->m_currentFileName = fileName;
2913 }
2914
2915 void FakeVimHandler::showBlackMessage(const QString &msg)
2916 {
2917    d->showBlackMessage(msg);
2918 }
2919
2920 void FakeVimHandler::showRedMessage(const QString &msg)
2921 {
2922    d->showRedMessage(msg);
2923 }
2924
2925 QWidget *FakeVimHandler::widget()
2926 {
2927     return d->editor();
2928 }
2929
2930 } // namespace Internal
2931 } // namespace FakeVim