1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and Digia.  For licensing terms and
13 ** conditions see http://qt.digia.com/licensing.  For further information
14 ** use the contact form at http://qt.digia.com/contact-us.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Digia gives you certain additional
25 ** rights.  These rights are described in the Digia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ****************************************************************************/
29 
30 //
31 // ATTENTION:
32 //
33 // 1 Please do not add any direct dependencies to other Qt Creator code here.
34 //   Instead emit signals and let the FakeVimPlugin channel the information to
35 //   Qt Creator. The idea is to keep this file here in a "clean" state that
36 //   allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
37 //
38 // 2 There are a few auto tests located in ../../../tests/auto/fakevim.
39 //   Commands that are covered there are marked as "// tested" below.
40 //
41 // 3 Some conventions:
42 //
43 //   Use 1 based line numbers and 0 based column numbers. Even though
44 //   the 1 based line are not nice it matches vim's and QTextEdit's 'line'
45 //   concepts.
46 //
47 //   Do not pass QTextCursor etc around unless really needed. Convert
48 //   early to  line/column.
49 //
50 //   A QTextCursor is always between characters, whereas vi's cursor is always
51 //   over a character. FakeVim interprets the QTextCursor to be over the character
52 //   to the right of the QTextCursor's position().
53 //
54 //   A current "region of interest"
55 //   spans between anchor(), (i.e. the character below anchor()), and
56 //   position(). The character below position() is not included
57 //   if the last movement command was exclusive (MoveExclusive).
58 //
59 
60 #include "fakevimhandler.h"
61 
62 #include "fakevimactions.h"
63 
64 #include <utils/hostosinfo.h>
65 #include <utils/qtcassert.h>
66 
67 #include <QDebug>
68 #include <QFile>
69 #include <QObject>
70 #include <QPointer>
71 #include <QProcess>
72 #include <QRegExp>
73 #include <QTextStream>
74 #include <QTimer>
75 #include <QStack>
76 
77 #include <QApplication>
78 #include <QClipboard>
79 #include <QInputMethodEvent>
80 #include <QKeyEvent>
81 #include <QLineEdit>
82 #include <QPlainTextEdit>
83 #include <QScrollBar>
84 #include <QTextBlock>
85 #include <QTextCursor>
86 #include <QTextDocumentFragment>
87 #include <QTextEdit>
88 #include <QMimeData>
89 #include <QSharedPointer>
90 
91 #include <algorithm>
92 #include <climits>
93 #include <ctype.h>
94 
95 //#define DEBUG_KEY  1
96 #if DEBUG_KEY
97 #   define KEY_DEBUG(s) qDebug() << s
98 #else
99 #   define KEY_DEBUG(s)
100 #endif
101 
102 //#define DEBUG_UNDO  1
103 #if DEBUG_UNDO
104 #   define UNDO_DEBUG(s) qDebug() << "REV" << revision() << s
105 #else
106 #   define UNDO_DEBUG(s)
107 #endif
108 
109 using namespace Utils;
110 #ifdef FAKEVIM_STANDALONE
111 using namespace FakeVim::Internal::Utils;
112 #endif
113 
114 namespace FakeVim {
115 namespace Internal {
116 
117 ///////////////////////////////////////////////////////////////////////
118 //
119 // FakeVimHandler
120 //
121 ///////////////////////////////////////////////////////////////////////
122 
123 #define StartOfLine     QTextCursor::StartOfLine
124 #define EndOfLine       QTextCursor::EndOfLine
125 #define MoveAnchor      QTextCursor::MoveAnchor
126 #define KeepAnchor      QTextCursor::KeepAnchor
127 #define Up              QTextCursor::Up
128 #define Down            QTextCursor::Down
129 #define Right           QTextCursor::Right
130 #define Left            QTextCursor::Left
131 #define EndOfDocument   QTextCursor::End
132 #define StartOfDocument QTextCursor::Start
133 #define NextBlock       QTextCursor::NextBlock
134 
135 #define ParagraphSeparator QChar::ParagraphSeparator
136 
137 #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
138 
139 #define MetaModifier     // Use HostOsInfo::controlModifier() instead
140 #define ControlModifier  // Use HostOsInfo::controlModifier() instead
141 
142 typedef QLatin1String _;
143 
144 /* Clipboard MIME types used by Vim. */
145 static const QString vimMimeText = _("_VIM_TEXT");
146 static const QString vimMimeTextEncoded = _("_VIMENC_TEXT");
147 
148 using namespace Qt;
149 
150 /*! A \e Mode represents one of the basic modes of operation of FakeVim.
151 */
152 
153 enum Mode
154 {
155     InsertMode,
156     ReplaceMode,
157     CommandMode,
158     ExMode
159 };
160 
161 enum BlockInsertMode
162 {
163     NoneBlockInsertMode,
164     AppendBlockInsertMode,
165     AppendToEndOfLineBlockInsertMode,
166     InsertBlockInsertMode,
167     ChangeBlockInsertMode
168 };
169 
170 /*! A \e SubMode is used for things that require one more data item
171     and are 'nested' behind a \l Mode.
172 */
173 enum SubMode
174 {
175     NoSubMode,
176     ChangeSubMode,       // Used for c
177     DeleteSubMode,       // Used for d
178     FilterSubMode,       // Used for !
179     IndentSubMode,       // Used for =
180     RegisterSubMode,     // Used for "
181     ShiftLeftSubMode,    // Used for <
182     ShiftRightSubMode,   // Used for >
183     InvertCaseSubMode,   // Used for g~
184     DownCaseSubMode,     // Used for gu
185     UpCaseSubMode,       // Used for gU
186     WindowSubMode,       // Used for Ctrl-w
187     YankSubMode,         // Used for y
188     ZSubMode,            // Used for z
189     CapitalZSubMode,     // Used for Z
190     ReplaceSubMode,      // Used for r
191     MacroRecordSubMode,  // Used for q
192     MacroExecuteSubMode, // Used for @
193     CtrlVSubMode         // Used for Ctrl-v in insert mode
194 };
195 
196 /*! A \e SubSubMode is used for things that require one more data item
197     and are 'nested' behind a \l SubMode.
198 */
199 enum SubSubMode
200 {
201     NoSubSubMode,
202     FtSubSubMode,          // Used for f, F, t, T.
203     MarkSubSubMode,        // Used for m.
204     BackTickSubSubMode,    // Used for `.
205     TickSubSubMode,        // Used for '.
206     TextObjectSubSubMode,  // Used for thing like iw, aW, as etc.
207     ZSubSubMode,           // Used for zj, zk
208     OpenSquareSubSubMode,  // Used for [{, {(, [z
209     CloseSquareSubSubMode, // Used for ]}, ]), ]z
210     SearchSubSubMode,
211     CtrlVUnicodeSubSubMode // Used for Ctrl-v based unicode input
212 };
213 
214 enum VisualMode
215 {
216     NoVisualMode,
217     VisualCharMode,
218     VisualLineMode,
219     VisualBlockMode
220 };
221 
222 enum MoveType
223 {
224     MoveExclusive,
225     MoveInclusive,
226     MoveLineWise
227 };
228 
229 /*!
230     \enum RangeMode
231 
232     The \e RangeMode serves as a means to define how the "Range" between
233     the \l cursor and the \l anchor position is to be interpreted.
234 
235     \value RangeCharMode   Entered by pressing \key v. The range includes
236                            all characters between cursor and anchor.
237     \value RangeLineMode   Entered by pressing \key V. The range includes
238                            all lines between the line of the cursor and
239                            the line of the anchor.
240     \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
241                            newline when deleting.
242     \value RangeBlockMode  Entered by pressing \key Ctrl-v. The range includes
243                            all characters with line and column coordinates
244                            between line and columns coordinates of cursor and
245                            anchor.
246     \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
247                            all characters in the affected lines up to the end
248                            of these lines.
249 */
250 
251 enum EventResult
252 {
253     EventHandled,
254     EventUnhandled,
255     EventCancelled, // Event is handled but a sub mode was cancelled.
256     EventPassedToCore
257 };
258 
259 struct CursorPosition
260 {
CursorPositionFakeVim::Internal::CursorPosition261     CursorPosition() : line(-1), column(-1) {}
CursorPositionFakeVim::Internal::CursorPosition262     CursorPosition(int block, int column) : line(block), column(column) {}
CursorPositionFakeVim::Internal::CursorPosition263     explicit CursorPosition(const QTextCursor &tc)
264         : line(tc.block().blockNumber()), column(tc.positionInBlock()) {}
CursorPositionFakeVim::Internal::CursorPosition265     CursorPosition(const QTextDocument *document, int position)
266     {
267         QTextBlock block = document->findBlock(position);
268         line = block.blockNumber();
269         column = position - block.position();
270     }
isValidFakeVim::Internal::CursorPosition271     bool isValid() const { return line >= 0 && column >= 0; }
operator >FakeVim::Internal::CursorPosition272     bool operator>(const CursorPosition &other) const
273         { return line > other.line || column > other.column; }
operator ==FakeVim::Internal::CursorPosition274     bool operator==(const CursorPosition &other) const
275         { return line == other.line && column == other.column; }
operator !=FakeVim::Internal::CursorPosition276     bool operator!=(const CursorPosition &other) const { return !operator==(other); }
277 
278     int line; // Line in document (from 0, folded lines included).
279     int column; // Position on line.
280 };
281 
operator <<(QDebug ts,const CursorPosition & pos)282 QDebug operator<<(QDebug ts, const CursorPosition &pos)
283 {
284     return ts << "(line: " << pos.line << ", column: " << pos.column << ")";
285 }
286 
287 class Mark
288 {
289 public:
Mark(const CursorPosition & pos=CursorPosition (),const QString & fileName=QString ())290     Mark(const CursorPosition &pos = CursorPosition(), const QString &fileName = QString())
291         : m_position(pos), m_fileName(fileName) {}
292 
isValid() const293     bool isValid() const { return m_position.isValid(); }
294 
isLocal(const QString & localFileName) const295     bool isLocal(const QString &localFileName) const
296     {
297         return m_fileName.isEmpty() || m_fileName == localFileName;
298     }
299 
300     /* Return position of mark within given document.
301      * If saved line number is too big, mark position is at the end of document.
302      * If line number is in document but column is too big, mark position is at the end of line.
303      */
position(const QTextDocument * document) const304     CursorPosition position(const QTextDocument *document) const
305     {
306         QTextBlock block = document->findBlockByNumber(m_position.line);
307         CursorPosition pos;
308         if (block.isValid()) {
309             pos.line = m_position.line;
310             pos.column = qMax(0, qMin(m_position.column, block.length() - 2));
311         } else if (document->isEmpty()) {
312             pos.line = 0;
313             pos.column = 0;
314         } else {
315             pos.line = document->blockCount() - 1;
316             pos.column = qMax(0, document->lastBlock().length() - 2);
317         }
318         return pos;
319     }
320 
fileName() const321     const QString &fileName() const { return m_fileName; }
322 
323 private:
324     CursorPosition m_position;
325     QString m_fileName;
326 };
327 typedef QHash<QChar, Mark> Marks;
328 typedef QHashIterator<QChar, Mark> MarksIterator;
329 
330 struct State
331 {
StateFakeVim::Internal::State332     State() : revision(-1), position(), marks(), lastVisualMode(NoVisualMode),
333         lastVisualModeInverted(false) {}
StateFakeVim::Internal::State334     State(int revision, const CursorPosition &position, const Marks &marks,
335         VisualMode lastVisualMode, bool lastVisualModeInverted) : revision(revision),
336         position(position), marks(marks), lastVisualMode(lastVisualMode),
337         lastVisualModeInverted(lastVisualModeInverted) {}
338 
isValidFakeVim::Internal::State339     bool isValid() const { return position.isValid(); }
340 
341     int revision;
342     CursorPosition position;
343     Marks marks;
344     VisualMode lastVisualMode;
345     bool lastVisualModeInverted;
346 };
347 
348 struct Column
349 {
ColumnFakeVim::Internal::Column350     Column(int p, int l) : physical(p), logical(l) {}
351     int physical; // Number of characters in the data.
352     int logical; // Column on screen.
353 };
354 
operator <<(QDebug ts,const Column & col)355 QDebug operator<<(QDebug ts, const Column &col)
356 {
357     return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
358 }
359 
360 struct Register
361 {
RegisterFakeVim::Internal::Register362     Register() : rangemode(RangeCharMode) {}
RegisterFakeVim::Internal::Register363     Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
RegisterFakeVim::Internal::Register364     Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
365     QString contents;
366     RangeMode rangemode;
367 };
368 
operator <<(QDebug ts,const Register & reg)369 QDebug operator<<(QDebug ts, const Register &reg)
370 {
371     return ts << reg.contents;
372 }
373 
374 struct SearchData
375 {
SearchDataFakeVim::Internal::SearchData376     SearchData()
377     {
378         forward = true;
379         highlightMatches = true;
380     }
381 
382     QString needle;
383     bool forward;
384     bool highlightMatches;
385 };
386 
387 // If string begins with given prefix remove it with trailing spaces and return true.
eatString(const char * prefix,QString * str)388 static bool eatString(const char *prefix, QString *str)
389 {
390     if (!str->startsWith(_(prefix)))
391         return false;
392     *str = str->mid(strlen(prefix)).trimmed();
393     return true;
394 }
395 
vimPatternToQtPattern(QString needle,bool ignoreCaseOption,bool smartCaseOption)396 static QRegExp vimPatternToQtPattern(QString needle, bool ignoreCaseOption, bool smartCaseOption)
397 {
398     /* Transformations (Vim regexp -> QRegExp):
399      *   \a -> [A-Za-z]
400      *   \A -> [^A-Za-z]
401      *   \h -> [A-Za-z_]
402      *   \H -> [^A-Za-z_]
403      *   \l -> [a-z]
404      *   \L -> [^a-z]
405      *   \o -> [0-7]
406      *   \O -> [^0-7]
407      *   \u -> [A-Z]
408      *   \U -> [^A-Z]
409      *   \x -> [0-9A-Fa-f]
410      *   \X -> [^0-9A-Fa-f]
411      *
412      *   \< -> \b
413      *   \> -> \b
414      *   [] -> \[\]
415      *   \= -> ?
416      *
417      *   (...)  <-> \(...\)
418      *   {...}  <-> \{...\}
419      *   |      <-> \|
420      *   ?      <-> \?
421      *   +      <-> \+
422      *   \{...} -> {...}
423      *
424      *   \c - set ignorecase for rest
425      *   \C - set noignorecase for rest
426      */
427     // FIXME: Option smartcase should be used only if search was typed by user.
428     bool ignorecase = ignoreCaseOption
429         && !(smartCaseOption && needle.contains(QRegExp(_("[A-Z]"))));
430     QString pattern;
431     pattern.reserve(2 * needle.size());
432 
433     bool escape = false;
434     bool brace = false;
435     bool embraced = false;
436     bool range = false;
437     bool curly = false;
438     foreach (const QChar &c, needle) {
439         if (brace) {
440             brace = false;
441             if (c == QLatin1Char(']')) {
442                 pattern.append(_("\\[\\]"));
443                 continue;
444             }
445             pattern.append(QLatin1Char('['));
446             escape = true;
447             embraced = true;
448         }
449         if (embraced) {
450             if (range) {
451                 QChar c2 = pattern[pattern.size() - 2];
452                 pattern.remove(pattern.size() - 2, 2);
453                 pattern.append(c2.toUpper() + QLatin1Char('-') + c.toUpper());
454                 pattern.append(c2.toLower() + QLatin1Char('-') + c.toLower());
455                 range = false;
456             } else if (escape) {
457                 escape = false;
458                 pattern.append(c);
459             } else if (c == QLatin1Char('\\')) {
460                 escape = true;
461             } else if (c == QLatin1Char(']')) {
462                 pattern.append(QLatin1Char(']'));
463                 embraced = false;
464             } else if (c == QLatin1Char('-')) {
465                 range = ignorecase && pattern[pattern.size() - 1].isLetter();
466                 pattern.append(QLatin1Char('-'));
467             } else if (c.isLetter() && ignorecase) {
468                 pattern.append(c.toLower()).append(c.toUpper());
469             } else {
470                 pattern.append(c);
471             }
472         } else if (QString::fromLatin1("(){}+|?").indexOf(c) != -1) {
473             if (c == QLatin1Char('{')) {
474                 curly = escape;
475             } else if (c == QLatin1Char('}') && curly) {
476                 curly = false;
477                 escape = true;
478             }
479 
480             if (escape)
481                 escape = false;
482             else
483                 pattern.append(QLatin1Char('\\'));
484             pattern.append(c);
485         } else if (escape) {
486             // escape expression
487             escape = false;
488             if (c == QLatin1Char('<') || c == QLatin1Char('>'))
489                 pattern.append(_("\\b"));
490             else if (c == QLatin1Char('a'))
491                 pattern.append(_("[a-zA-Z]"));
492             else if (c == QLatin1Char('A'))
493                 pattern.append(_("[^a-zA-Z]"));
494             else if (c == QLatin1Char('h'))
495                 pattern.append(_("[A-Za-z_]"));
496             else if (c == QLatin1Char('H'))
497                 pattern.append(_("[^A-Za-z_]"));
498             else if (c == QLatin1Char('c') || c == QLatin1Char('C'))
499                 ignorecase = (c == QLatin1Char('c'));
500             else if (c == QLatin1Char('l'))
501                 pattern.append(_("[a-z]"));
502             else if (c == QLatin1Char('L'))
503                 pattern.append(_("[^a-z]"));
504             else if (c == QLatin1Char('o'))
505                 pattern.append(_("[0-7]"));
506             else if (c == QLatin1Char('O'))
507                 pattern.append(_("[^0-7]"));
508             else if (c == QLatin1Char('u'))
509                 pattern.append(_("[A-Z]"));
510             else if (c == QLatin1Char('U'))
511                 pattern.append(_("[^A-Z]"));
512             else if (c == QLatin1Char('x'))
513                 pattern.append(_("[0-9A-Fa-f]"));
514             else if (c == QLatin1Char('X'))
515                 pattern.append(_("[^0-9A-Fa-f]"));
516             else if (c == QLatin1Char('='))
517                 pattern.append(_("?"));
518             else
519                 pattern.append(QLatin1Char('\\') + c);
520         } else {
521             // unescaped expression
522             if (c == QLatin1Char('\\'))
523                 escape = true;
524             else if (c == QLatin1Char('['))
525                 brace = true;
526             else if (c.isLetter() && ignorecase)
527                 pattern.append(QLatin1Char('[') + c.toLower() + c.toUpper() + QLatin1Char(']'));
528             else
529                 pattern.append(c);
530         }
531     }
532     if (escape)
533         pattern.append(QLatin1Char('\\'));
534     else if (brace)
535         pattern.append(QLatin1Char('['));
536 
537     return QRegExp(pattern);
538 }
539 
afterEndOfLine(const QTextDocument * doc,int position)540 static bool afterEndOfLine(const QTextDocument *doc, int position)
541 {
542     return doc->characterAt(position) == ParagraphSeparator
543         && doc->findBlock(position).length() > 1;
544 }
545 
searchForward(QTextCursor * tc,QRegExp & needleExp,int * repeat)546 static void searchForward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
547 {
548     const QTextDocument *doc = tc->document();
549     const int startPos = tc->position();
550 
551     // Search from beginning of line so that matched text is the same.
552     tc->movePosition(StartOfLine);
553 
554     // forward to current position
555     *tc = doc->find(needleExp, *tc);
556     while (!tc->isNull() && tc->anchor() < startPos) {
557         if (!tc->hasSelection())
558             tc->movePosition(Right);
559         if (tc->atBlockEnd())
560             tc->movePosition(NextBlock);
561         *tc = doc->find(needleExp, *tc);
562     }
563 
564     if (tc->isNull())
565         return;
566 
567     --*repeat;
568 
569     while (*repeat > 0) {
570         if (!tc->hasSelection())
571             tc->movePosition(Right);
572         if (tc->atBlockEnd())
573             tc->movePosition(NextBlock);
574         *tc = doc->find(needleExp, *tc);
575         if (tc->isNull())
576             return;
577         --*repeat;
578     }
579 
580     if (!tc->isNull() && afterEndOfLine(doc, tc->anchor()))
581         tc->movePosition(Left);
582 }
583 
searchBackward(QTextCursor * tc,QRegExp & needleExp,int * repeat)584 static void searchBackward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
585 {
586     // Search from beginning of line so that matched text is the same.
587     QTextBlock block = tc->block();
588     QString line = block.text();
589 
590     int i = line.indexOf(needleExp, 0);
591     while (i != -1 && i < tc->positionInBlock()) {
592         --*repeat;
593         i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
594         if (i == line.size())
595             i = -1;
596     }
597 
598     if (i == tc->positionInBlock())
599         --*repeat;
600 
601     while (*repeat > 0) {
602         block = block.previous();
603         if (!block.isValid())
604             break;
605         line = block.text();
606         i = line.indexOf(needleExp, 0);
607         while (i != -1) {
608             --*repeat;
609             i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
610             if (i == line.size())
611                 i = -1;
612         }
613     }
614 
615     if (!block.isValid()) {
616         *tc = QTextCursor();
617         return;
618     }
619 
620     i = line.indexOf(needleExp, 0);
621     while (*repeat < 0) {
622         i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
623         ++*repeat;
624     }
625     tc->setPosition(block.position() + i);
626     tc->setPosition(tc->position() + needleExp.matchedLength(), KeepAnchor);
627 }
628 
629 // Commands [[, []
bracketSearchBackward(QTextCursor * tc,const QString & needleExp,int repeat)630 static void bracketSearchBackward(QTextCursor *tc, const QString &needleExp, int repeat)
631 {
632     QRegExp re(needleExp);
633     QTextCursor tc2 = *tc;
634     tc2.setPosition(tc2.position() - 1);
635     searchBackward(&tc2, re, &repeat);
636     if (repeat <= 1)
637         tc->setPosition(tc2.isNull() ? 0 : tc2.position(), KeepAnchor);
638 }
639 
640 // Commands ][, ]]
641 // When ]] is used after an operator, then also stops below a '}' in the first column.
bracketSearchForward(QTextCursor * tc,const QString & needleExp,int repeat,bool searchWithCommand)642 static void bracketSearchForward(QTextCursor *tc, const QString &needleExp, int repeat,
643                                  bool searchWithCommand)
644 {
645     QRegExp re(searchWithCommand ? QString(_("^\\}|^\\{")) : needleExp);
646     QTextCursor tc2 = *tc;
647     tc2.setPosition(tc2.position() + 1);
648     searchForward(&tc2, re, &repeat);
649     if (repeat <= 1) {
650         if (tc2.isNull()) {
651             tc->setPosition(tc->document()->characterCount() - 1, KeepAnchor);
652         } else {
653             tc->setPosition(tc2.position() - 1, KeepAnchor);
654             if (searchWithCommand && tc->document()->characterAt(tc->position()).unicode() == '}') {
655                 QTextBlock block = tc->block().next();
656                 if (block.isValid())
657                     tc->setPosition(block.position(), KeepAnchor);
658             }
659         }
660     }
661 }
662 
substituteText(QString * text,QRegExp & pattern,const QString & replacement,bool global)663 static bool substituteText(QString *text, QRegExp &pattern, const QString &replacement,
664     bool global)
665 {
666     bool substituted = false;
667     int pos = 0;
668     int right = -1;
669     while (true) {
670         pos = pattern.indexIn(*text, pos, QRegExp::CaretAtZero);
671         if (pos == -1)
672             break;
673 
674         // ensure that substitution is advancing towards end of line
675         if (right == text->size() - pos) {
676             ++pos;
677             if (pos == text->size())
678                 break;
679             continue;
680         }
681 
682         right = text->size() - pos;
683 
684         substituted = true;
685         QString matched = text->mid(pos, pattern.cap(0).size());
686         QString repl;
687         bool escape = false;
688         // insert captured texts
689         for (int i = 0; i < replacement.size(); ++i) {
690             const QChar &c = replacement[i];
691             if (escape) {
692                 escape = false;
693                 if (c.isDigit()) {
694                     if (c.digitValue() <= pattern.captureCount())
695                         repl += pattern.cap(c.digitValue());
696                 } else {
697                     repl += c;
698                 }
699             } else {
700                 if (c == QLatin1Char('\\'))
701                     escape = true;
702                 else if (c == QLatin1Char('&'))
703                     repl += pattern.cap(0);
704                 else
705                     repl += c;
706             }
707         }
708         text->replace(pos, matched.size(), repl);
709         pos += (repl.isEmpty() && matched.isEmpty()) ? 1 : repl.size();
710 
711         if (pos >= text->size() || !global)
712             break;
713     }
714 
715     return substituted;
716 }
717 
findUnescaped(QChar c,const QString & line,int from)718 static int findUnescaped(QChar c, const QString &line, int from)
719 {
720     for (int i = from; i < line.size(); ++i) {
721         if (line.at(i) == c && (i == 0 || line.at(i - 1) != QLatin1Char('\\')))
722             return i;
723     }
724     return -1;
725 }
726 
setClipboardData(const QString & content,RangeMode mode,QClipboard::Mode clipboardMode)727 static void setClipboardData(const QString &content, RangeMode mode,
728     QClipboard::Mode clipboardMode)
729 {
730     QClipboard *clipboard = QApplication::clipboard();
731     char vimRangeMode = mode;
732 
733     QByteArray bytes1;
734     bytes1.append(vimRangeMode);
735     bytes1.append(content.toUtf8());
736 
737     QByteArray bytes2;
738     bytes2.append(vimRangeMode);
739     bytes2.append("utf-8");
740     bytes2.append('\0');
741     bytes2.append(content.toUtf8());
742 
743     QMimeData *data = new QMimeData;
744     data->setText(content);
745     data->setData(vimMimeText, bytes1);
746     data->setData(vimMimeTextEncoded, bytes2);
747     clipboard->setMimeData(data, clipboardMode);
748 }
749 
toLocalEncoding(const QString & text)750 static QByteArray toLocalEncoding(const QString &text)
751 {
752     return HostOsInfo::isWindowsHost() ? QString(text).replace(_("\n"), _("\r\n")).toLocal8Bit()
753                                        : text.toLocal8Bit();
754 }
755 
fromLocalEncoding(const QByteArray & data)756 static QString fromLocalEncoding(const QByteArray &data)
757 {
758     return HostOsInfo::isWindowsHost() ? QString::fromLocal8Bit(data).replace(_("\n"), _("\r\n"))
759                                        : QString::fromLocal8Bit(data);
760 }
761 
getProcessOutput(const QString & command,const QString & input)762 static QString getProcessOutput(const QString &command, const QString &input)
763 {
764     QProcess proc;
765     proc.start(command);
766     proc.waitForStarted();
767     proc.write(toLocalEncoding(input));
768     proc.closeWriteChannel();
769 
770     // FIXME: Process should be interruptable by user.
771     //        Solution is to create a QObject for each process and emit finished state.
772     proc.waitForFinished();
773 
774     return fromLocalEncoding(proc.readAllStandardOutput());
775 }
776 
vimKeyNames()777 static const QMap<QString, int> &vimKeyNames()
778 {
779     static QMap<QString, int> k;
780     if (!k.isEmpty())
781         return k;
782 
783     // FIXME: Should be value of mapleader.
784     k.insert(_("LEADER"), Key_Backslash);
785 
786     k.insert(_("SPACE"), Key_Space);
787     k.insert(_("TAB"), Key_Tab);
788     k.insert(_("NL"), Key_Return);
789     k.insert(_("NEWLINE"), Key_Return);
790     k.insert(_("LINEFEED"), Key_Return);
791     k.insert(_("LF"), Key_Return);
792     k.insert(_("CR"), Key_Return);
793     k.insert(_("RETURN"), Key_Return);
794     k.insert(_("ENTER"), Key_Return);
795     k.insert(_("BS"), Key_Backspace);
796     k.insert(_("BACKSPACE"), Key_Backspace);
797     k.insert(_("ESC"), Key_Escape);
798     k.insert(_("BAR"), Key_Bar);
799     k.insert(_("BSLASH"), Key_Backslash);
800     k.insert(_("DEL"), Key_Delete);
801     k.insert(_("DELETE"), Key_Delete);
802     k.insert(_("KDEL"), Key_Delete);
803     k.insert(_("UP"), Key_Up);
804     k.insert(_("DOWN"), Key_Down);
805     k.insert(_("LEFT"), Key_Left);
806     k.insert(_("RIGHT"), Key_Right);
807 
808     k.insert(_("LT"), Key_Less);
809     k.insert(_("GT"), Key_Greater);
810 
811     k.insert(_("F1"), Key_F1);
812     k.insert(_("F2"), Key_F2);
813     k.insert(_("F3"), Key_F3);
814     k.insert(_("F4"), Key_F4);
815     k.insert(_("F5"), Key_F5);
816     k.insert(_("F6"), Key_F6);
817     k.insert(_("F7"), Key_F7);
818     k.insert(_("F8"), Key_F8);
819     k.insert(_("F9"), Key_F9);
820     k.insert(_("F10"), Key_F10);
821 
822     k.insert(_("F11"), Key_F11);
823     k.insert(_("F12"), Key_F12);
824     k.insert(_("F13"), Key_F13);
825     k.insert(_("F14"), Key_F14);
826     k.insert(_("F15"), Key_F15);
827     k.insert(_("F16"), Key_F16);
828     k.insert(_("F17"), Key_F17);
829     k.insert(_("F18"), Key_F18);
830     k.insert(_("F19"), Key_F19);
831     k.insert(_("F20"), Key_F20);
832 
833     k.insert(_("F21"), Key_F21);
834     k.insert(_("F22"), Key_F22);
835     k.insert(_("F23"), Key_F23);
836     k.insert(_("F24"), Key_F24);
837     k.insert(_("F25"), Key_F25);
838     k.insert(_("F26"), Key_F26);
839     k.insert(_("F27"), Key_F27);
840     k.insert(_("F28"), Key_F28);
841     k.insert(_("F29"), Key_F29);
842     k.insert(_("F30"), Key_F30);
843 
844     k.insert(_("F31"), Key_F31);
845     k.insert(_("F32"), Key_F32);
846     k.insert(_("F33"), Key_F33);
847     k.insert(_("F34"), Key_F34);
848     k.insert(_("F35"), Key_F35);
849 
850     k.insert(_("INSERT"), Key_Insert);
851     k.insert(_("INS"), Key_Insert);
852     k.insert(_("KINSERT"), Key_Insert);
853     k.insert(_("HOME"), Key_Home);
854     k.insert(_("END"), Key_End);
855     k.insert(_("PAGEUP"), Key_PageUp);
856     k.insert(_("PAGEDOWN"), Key_PageDown);
857 
858     k.insert(_("KPLUS"), Key_Plus);
859     k.insert(_("KMINUS"), Key_Minus);
860     k.insert(_("KDIVIDE"), Key_Slash);
861     k.insert(_("KMULTIPLY"), Key_Asterisk);
862     k.insert(_("KENTER"), Key_Enter);
863     k.insert(_("KPOINT"), Key_Period);
864 
865     return k;
866 }
867 
isOnlyControlModifier(const Qt::KeyboardModifiers & mods)868 static bool isOnlyControlModifier(const Qt::KeyboardModifiers &mods)
869 {
870     return (mods ^ HostOsInfo::controlModifier()) == Qt::NoModifier;
871 }
872 
873 
Range()874 Range::Range()
875     : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
876 {}
877 
Range(int b,int e,RangeMode m)878 Range::Range(int b, int e, RangeMode m)
879     : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
880 {}
881 
toString() const882 QString Range::toString() const
883 {
884     return QString::fromLatin1("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
885         .arg(rangemode);
886 }
887 
isValid() const888 bool Range::isValid() const
889 {
890     return beginPos >= 0 && endPos >= 0;
891 }
892 
operator <<(QDebug ts,const Range & range)893 QDebug operator<<(QDebug ts, const Range &range)
894 {
895     return ts << '[' << range.beginPos << ',' << range.endPos << ']';
896 }
897 
898 
ExCommand(const QString & c,const QString & a,const Range & r)899 ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
900     : cmd(c), hasBang(false), args(a), range(r), count(1)
901 {}
902 
matches(const QString & min,const QString & full) const903 bool ExCommand::matches(const QString &min, const QString &full) const
904 {
905     return cmd.startsWith(min) && full.startsWith(cmd);
906 }
907 
operator <<(QDebug ts,const ExCommand & cmd)908 QDebug operator<<(QDebug ts, const ExCommand &cmd)
909 {
910     return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
911 }
912 
operator <<(QDebug ts,const QList<QTextEdit::ExtraSelection> & sels)913 QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
914 {
915     foreach (const QTextEdit::ExtraSelection &sel, sels)
916         ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
917     return ts;
918 }
919 
quoteUnprintable(const QString & ba)920 QString quoteUnprintable(const QString &ba)
921 {
922     QString res;
923     for (int i = 0, n = ba.size(); i != n; ++i) {
924         const QChar c = ba.at(i);
925         const int cc = c.unicode();
926         if (c.isPrint())
927             res += c;
928         else if (cc == QLatin1Char('\n'))
929             res += _("<CR>");
930         else
931             res += QString::fromLatin1("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
932     }
933     return res;
934 }
935 
startsWithWhitespace(const QString & str,int col)936 static bool startsWithWhitespace(const QString &str, int col)
937 {
938     QTC_ASSERT(str.size() >= col, return false);
939     for (int i = 0; i < col; ++i) {
940         uint u = str.at(i).unicode();
941         if (u != QLatin1Char(' ') && u != QLatin1Char('\t'))
942             return false;
943     }
944     return true;
945 }
946 
msgMarkNotSet(const QString & text)947 inline QString msgMarkNotSet(const QString &text)
948 {
949     return FakeVimHandler::tr("Mark \"%1\" not set.").arg(text);
950 }
951 
952 class Input
953 {
954 public:
955     // Remove some extra "information" on Mac.
cleanModifier(Qt::KeyboardModifiers m)956     static Qt::KeyboardModifiers cleanModifier(Qt::KeyboardModifiers m)
957     {
958         return m & ~Qt::KeypadModifier;
959     }
960 
Input()961     Input()
962         : m_key(0), m_xkey(0), m_modifiers(0) {}
963 
Input(QChar x)964     explicit Input(QChar x)
965         : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x)
966     {
967         if (x.isUpper())
968             m_modifiers = Qt::ShiftModifier;
969         else if (x.isLower())
970             m_key = x.toUpper().unicode();
971     }
972 
Input(int k,Qt::KeyboardModifiers m,const QString & t=QString ())973     Input(int k, Qt::KeyboardModifiers m, const QString &t = QString())
974         : m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
975     {
976         if (m_text.size() == 1) {
977             QChar x = m_text.at(0);
978 
979             // On Mac, QKeyEvent::text() returns non-empty strings for
980             // cursor keys. This breaks some of the logic later on
981             // relying on text() being empty for "special" keys.
982             // FIXME: Check the real conditions.
983             if (x.unicode() < ' ')
984                 m_text.clear();
985             else if (x.isLetter())
986                 m_key = x.toUpper().unicode();
987         }
988 
989         // Set text only if input is ascii key without control modifier.
990         if (m_text.isEmpty() && k >= 0 && k <= 0x7f && (m & HostOsInfo::controlModifier()) == 0) {
991             QChar c = QChar::fromLatin1(k);
992             if (c.isLetter())
993                 m_text = QString(isShift() ? c.toUpper() : c);
994             else if (!isShift())
995                 m_text = c;
996         }
997 
998         // Normalize <S-TAB>.
999         if (m_key == Qt::Key_Backtab) {
1000             m_key = Qt::Key_Tab;
1001             m_modifiers |= Qt::ShiftModifier;
1002         }
1003 
1004         // m_xkey is only a cache.
1005         m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
1006     }
1007 
isValid() const1008     bool isValid() const
1009     {
1010         return m_key != 0 || !m_text.isNull();
1011     }
1012 
isDigit() const1013     bool isDigit() const
1014     {
1015         return m_xkey >= '0' && m_xkey <= '9';
1016     }
1017 
isKey(int c) const1018     bool isKey(int c) const
1019     {
1020         return !m_modifiers && m_key == c;
1021     }
1022 
isBackspace() const1023     bool isBackspace() const
1024     {
1025         return m_key == Key_Backspace || isControl('h');
1026     }
1027 
isReturn() const1028     bool isReturn() const
1029     {
1030         return m_key == QLatin1Char('\n') || m_key == Key_Return || m_key == Key_Enter;
1031     }
1032 
isEscape() const1033     bool isEscape() const
1034     {
1035         return isKey(Key_Escape) || isKey(27) || isControl('c')
1036             || isControl(Key_BracketLeft);
1037     }
1038 
is(int c) const1039     bool is(int c) const
1040     {
1041         return m_xkey == c && !isControl();
1042     }
1043 
isControl() const1044     bool isControl() const
1045     {
1046         return isOnlyControlModifier(m_modifiers);
1047     }
1048 
isControl(int c) const1049     bool isControl(int c) const
1050     {
1051         return isControl()
1052             && (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
1053     }
1054 
isShift() const1055     bool isShift() const
1056     {
1057         return m_modifiers & Qt::ShiftModifier;
1058     }
1059 
isShift(int c) const1060     bool isShift(int c) const
1061     {
1062         return isShift() && m_xkey == c;
1063     }
1064 
operator <(const Input & a) const1065     bool operator<(const Input &a) const
1066     {
1067         if (m_key != a.m_key)
1068             return m_key < a.m_key;
1069         // Text for some mapped key cannot be determined (e.g. <C-J>) so if text is not set for
1070         // one of compared keys ignore it.
1071         if (!m_text.isEmpty() && !a.m_text.isEmpty() && m_text != _(" "))
1072             return m_text < a.m_text;
1073         return m_modifiers < a.m_modifiers;
1074     }
1075 
operator ==(const Input & a) const1076     bool operator==(const Input &a) const
1077     {
1078         return !(*this < a || a < *this);
1079     }
1080 
operator !=(const Input & a) const1081     bool operator!=(const Input &a) const { return !operator==(a); }
1082 
text() const1083     QString text() const { return m_text; }
1084 
asChar() const1085     QChar asChar() const
1086     {
1087         return (m_text.size() == 1 ? m_text.at(0) : QChar());
1088     }
1089 
toInt(bool * ok,int base) const1090     int toInt(bool *ok, int base) const
1091     {
1092         const int uc = asChar().unicode();
1093         int res;
1094         if ('0' <= uc && uc <= '9')
1095             res = uc -'0';
1096         else if ('a' <= uc && uc <= 'z')
1097             res = 10 + uc - 'a';
1098         else if ('A' <= uc && uc <= 'Z')
1099             res = 10 + uc - 'A';
1100         else
1101             res = base;
1102         *ok = res < base;
1103         return *ok ? res : 0;
1104     }
1105 
key() const1106     int key() const { return m_key; }
1107 
modifiers() const1108     Qt::KeyboardModifiers modifiers() const { return m_modifiers; }
1109 
1110     // Return raw character for macro recording or dot command.
raw() const1111     QChar raw() const
1112     {
1113         if (m_key == Key_Tab)
1114             return QLatin1Char('\t');
1115         if (m_key == Key_Return)
1116             return QLatin1Char('\n');
1117         if (m_key == Key_Escape)
1118             return QChar(27);
1119         return m_xkey;
1120     }
1121 
toString() const1122     QString toString() const
1123     {
1124         QString key = vimKeyNames().key(m_key);
1125         bool namedKey = !key.isEmpty();
1126 
1127         if (!namedKey) {
1128             if (m_xkey == '<')
1129                 key = _("<LT>");
1130             else if (m_xkey == '>')
1131                 key = _("<GT>");
1132             else
1133                 key = QChar(m_xkey);
1134         }
1135 
1136         bool shift = isShift();
1137         bool ctrl = isControl();
1138         if (shift)
1139             key.prepend(_("S-"));
1140         if (ctrl)
1141             key.prepend(_("C-"));
1142 
1143         if (namedKey || shift || ctrl) {
1144             key.prepend(QLatin1Char('<'));
1145             key.append(QLatin1Char('>'));
1146         }
1147 
1148         return key;
1149     }
1150 
dump(QDebug ts) const1151     QDebug dump(QDebug ts) const
1152     {
1153         return ts << m_key << '-' << m_modifiers << '-'
1154             << quoteUnprintable(m_text);
1155     }
1156 private:
1157     int m_key;
1158     int m_xkey;
1159     Qt::KeyboardModifiers m_modifiers;
1160     QString m_text;
1161 };
1162 
1163 // mapping to <Nop> (do nothing)
1164 static const Input Nop(-1, Qt::KeyboardModifiers(-1), QString());
1165 
operator <<(QDebug ts,const Input & input)1166 QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }
1167 
1168 class Inputs : public QVector<Input>
1169 {
1170 public:
Inputs()1171     Inputs() : m_noremap(true), m_silent(false) {}
1172 
Inputs(const QString & str,bool noremap=true,bool silent=false)1173     explicit Inputs(const QString &str, bool noremap = true, bool silent = false)
1174         : m_noremap(noremap), m_silent(silent)
1175     {
1176         parseFrom(str);
1177         squeeze();
1178     }
1179 
noremap() const1180     bool noremap() const { return m_noremap; }
1181 
silent() const1182     bool silent() const { return m_silent; }
1183 
1184 private:
1185     void parseFrom(const QString &str);
1186 
1187     bool m_noremap;
1188     bool m_silent;
1189 };
1190 
parseVimKeyName(const QString & keyName)1191 static Input parseVimKeyName(const QString &keyName)
1192 {
1193     if (keyName.length() == 1)
1194         return Input(keyName.at(0));
1195 
1196     const QStringList keys = keyName.split(QLatin1Char('-'));
1197     const int len = keys.length();
1198 
1199     if (len == 1 && keys.at(0).toUpper() == _("NOP"))
1200         return Nop;
1201 
1202     Qt::KeyboardModifiers mods = NoModifier;
1203     for (int i = 0; i < len - 1; ++i) {
1204         const QString &key = keys[i].toUpper();
1205         if (key == _("S"))
1206             mods |= Qt::ShiftModifier;
1207         else if (key == _("C"))
1208             mods |= HostOsInfo::controlModifier();
1209         else
1210             return Input();
1211     }
1212 
1213     if (!keys.isEmpty()) {
1214         const QString key = keys.last();
1215         if (key.length() == 1) {
1216             // simple character
1217             QChar c = key.at(0).toUpper();
1218             return Input(c.unicode(), mods);
1219         }
1220 
1221         // find key name
1222         QMap<QString, int>::ConstIterator it = vimKeyNames().constFind(key.toUpper());
1223         if (it != vimKeyNames().end())
1224             return Input(*it, mods);
1225     }
1226 
1227     return Input();
1228 }
1229 
parseFrom(const QString & str)1230 void Inputs::parseFrom(const QString &str)
1231 {
1232     const int n = str.size();
1233     for (int i = 0; i < n; ++i) {
1234         ushort c = str.at(i).unicode();
1235         if (c == '<') {
1236             int j = str.indexOf(QLatin1Char('>'), i);
1237             Input input;
1238             if (j != -1) {
1239                 const QString key = str.mid(i+1, j - i - 1);
1240                 if (!key.contains(QLatin1Char('<')))
1241                     input = parseVimKeyName(key);
1242             }
1243             if (input.isValid()) {
1244                 append(input);
1245                 i = j;
1246             } else {
1247                 append(Input(c));
1248             }
1249         } else {
1250             append(Input(c));
1251         }
1252     }
1253 }
1254 
1255 class History
1256 {
1257 public:
History()1258     History() : m_items(QString()), m_index(0) {}
1259     void append(const QString &item);
1260     const QString &move(const QStringRef &prefix, int skip);
current() const1261     const QString &current() const { return m_items[m_index]; }
items() const1262     const QStringList &items() const { return m_items; }
restart()1263     void restart() { m_index = m_items.size() - 1; }
1264 
1265 private:
1266     // Last item is always empty or current search prefix.
1267     QStringList m_items;
1268     int m_index;
1269 };
1270 
append(const QString & item)1271 void History::append(const QString &item)
1272 {
1273     if (item.isEmpty())
1274         return;
1275     m_items.pop_back();
1276     m_items.removeAll(item);
1277     m_items << item << QString();
1278     restart();
1279 }
1280 
move(const QStringRef & prefix,int skip)1281 const QString &History::move(const QStringRef &prefix, int skip)
1282 {
1283     if (!current().startsWith(prefix))
1284         restart();
1285 
1286     if (m_items.last() != prefix)
1287         m_items[m_items.size() - 1] = prefix.toString();
1288 
1289     int i = m_index + skip;
1290     if (!prefix.isEmpty())
1291         for (; i >= 0 && i < m_items.size() && !m_items[i].startsWith(prefix); i += skip)
1292             ;
1293     if (i >= 0 && i < m_items.size())
1294         m_index = i;
1295 
1296     return current();
1297 }
1298 
1299 // Command line buffer with prompt (i.e. :, / or ? characters), text contents and cursor position.
1300 class CommandBuffer
1301 {
1302 public:
CommandBuffer()1303     CommandBuffer() : m_pos(0), m_anchor(0), m_userPos(0), m_historyAutoSave(true) {}
1304 
setPrompt(const QChar & prompt)1305     void setPrompt(const QChar &prompt) { m_prompt = prompt; }
setContents(const QString & s)1306     void setContents(const QString &s) { m_buffer = s; m_anchor = m_pos = s.size(); }
1307 
setContents(const QString & s,int pos,int anchor=-1)1308     void setContents(const QString &s, int pos, int anchor = -1)
1309     {
1310         m_buffer = s; m_pos = m_userPos = pos; m_anchor = anchor >= 0 ? anchor : pos;
1311     }
1312 
userContents() const1313     QStringRef userContents() const { return m_buffer.leftRef(m_userPos); }
prompt() const1314     const QChar &prompt() const { return m_prompt; }
contents() const1315     const QString &contents() const { return m_buffer; }
isEmpty() const1316     bool isEmpty() const { return m_buffer.isEmpty(); }
cursorPos() const1317     int cursorPos() const { return m_pos; }
anchorPos() const1318     int anchorPos() const { return m_anchor; }
hasSelection() const1319     bool hasSelection() const { return m_pos != m_anchor; }
1320 
insertChar(QChar c)1321     void insertChar(QChar c) { m_buffer.insert(m_pos++, c); m_anchor = m_userPos = m_pos; }
insertText(const QString & s)1322     void insertText(const QString &s)
1323     {
1324         m_buffer.insert(m_pos, s); m_anchor = m_userPos = m_pos = m_pos + s.size();
1325     }
deleteChar()1326     void deleteChar() { if (m_pos) m_buffer.remove(--m_pos, 1); m_anchor = m_userPos = m_pos; }
1327 
moveLeft()1328     void moveLeft() { if (m_pos) m_userPos = --m_pos; }
moveRight()1329     void moveRight() { if (m_pos < m_buffer.size()) m_userPos = ++m_pos; }
moveStart()1330     void moveStart() { m_userPos = m_pos = 0; }
moveEnd()1331     void moveEnd() { m_userPos = m_pos = m_buffer.size(); }
1332 
setHistoryAutoSave(bool autoSave)1333     void setHistoryAutoSave(bool autoSave) { m_historyAutoSave = autoSave; }
historyDown()1334     void historyDown() { setContents(m_history.move(userContents(), 1)); }
historyUp()1335     void historyUp() { setContents(m_history.move(userContents(), -1)); }
historyItems() const1336     const QStringList &historyItems() const { return m_history.items(); }
historyPush(const QString & item=QString ())1337     void historyPush(const QString &item = QString())
1338     {
1339         m_history.append(item.isNull() ? contents() : item);
1340     }
1341 
clear()1342     void clear()
1343     {
1344         if (m_historyAutoSave)
1345             historyPush();
1346         m_buffer.clear();
1347         m_anchor = m_userPos = m_pos = 0;
1348     }
1349 
display() const1350     QString display() const
1351     {
1352         QString msg(m_prompt);
1353         for (int i = 0; i != m_buffer.size(); ++i) {
1354             const QChar c = m_buffer.at(i);
1355             if (c.unicode() < 32) {
1356                 msg += QLatin1Char('^');
1357                 msg += QChar(c.unicode() + 64);
1358             } else {
1359                 msg += c;
1360             }
1361         }
1362         return msg;
1363     }
1364 
deleteSelected()1365     void deleteSelected()
1366     {
1367         if (m_pos < m_anchor) {
1368             m_buffer.remove(m_pos, m_anchor - m_pos);
1369             m_anchor = m_pos;
1370         } else {
1371             m_buffer.remove(m_anchor, m_pos - m_anchor);
1372             m_pos = m_anchor;
1373         }
1374     }
1375 
handleInput(const Input & input)1376     bool handleInput(const Input &input)
1377     {
1378         if (input.isShift(Key_Left)) {
1379             moveLeft();
1380         } else if (input.isShift(Key_Right)) {
1381             moveRight();
1382         } else if (input.isShift(Key_Home)) {
1383             moveStart();
1384         } else if (input.isShift(Key_End)) {
1385             moveEnd();
1386         } else if (input.isKey(Key_Left)) {
1387             moveLeft();
1388             m_anchor = m_pos;
1389         } else if (input.isKey(Key_Right)) {
1390             moveRight();
1391             m_anchor = m_pos;
1392         } else if (input.isKey(Key_Home)) {
1393             moveStart();
1394             m_anchor = m_pos;
1395         } else if (input.isKey(Key_End)) {
1396             moveEnd();
1397             m_anchor = m_pos;
1398         } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
1399             historyUp();
1400         } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
1401             historyDown();
1402         } else if (input.isKey(Key_Delete)) {
1403             if (hasSelection()) {
1404                 deleteSelected();
1405             } else {
1406                 if (m_pos < m_buffer.size())
1407                     m_buffer.remove(m_pos, 1);
1408                 else
1409                     deleteChar();
1410             }
1411         } else if (!input.text().isEmpty()) {
1412             if (hasSelection())
1413                 deleteSelected();
1414             insertText(input.text());
1415         } else {
1416             return false;
1417         }
1418         return true;
1419     }
1420 
1421 private:
1422     QString m_buffer;
1423     QChar m_prompt;
1424     History m_history;
1425     int m_pos;
1426     int m_anchor;
1427     int m_userPos; // last position of inserted text (for retrieving history items)
1428     bool m_historyAutoSave; // store items to history on clear()?
1429 };
1430 
1431 // Mappings for a specific mode (trie structure)
1432 class ModeMapping : public QMap<Input, ModeMapping>
1433 {
1434 public:
value() const1435     const Inputs &value() const { return m_value; }
setValue(const Inputs & value)1436     void setValue(const Inputs &value) { m_value = value; }
1437 private:
1438     Inputs m_value;
1439 };
1440 
1441 // Mappings for all modes
1442 typedef QHash<char, ModeMapping> Mappings;
1443 
1444 // Iterator for mappings
1445 class MappingsIterator : public QVector<ModeMapping::Iterator>
1446 {
1447 public:
MappingsIterator(Mappings * mappings,char mode=-1,const Inputs & inputs=Inputs ())1448     MappingsIterator(Mappings *mappings, char mode = -1, const Inputs &inputs = Inputs())
1449         : m_parent(mappings)
1450         , m_lastValid(-1)
1451         , m_mode(0)
1452     {
1453         reset(mode);
1454         walk(inputs);
1455     }
1456 
1457     // Reset iterator state. Keep previous mode if 0.
reset(char mode=0)1458     void reset(char mode = 0)
1459     {
1460         clear();
1461         m_lastValid = -1;
1462         m_currentInputs.clear();
1463         if (mode != 0) {
1464             m_mode = mode;
1465             if (mode != -1)
1466                 m_modeMapping = m_parent->find(mode);
1467         }
1468     }
1469 
isValid() const1470     bool isValid() const { return !empty(); }
1471 
1472     // Return true if mapping can be extended.
canExtend() const1473     bool canExtend() const { return isValid() && !last()->empty(); }
1474 
1475     // Return true if this mapping can be used.
isComplete() const1476     bool isComplete() const { return m_lastValid != -1; }
1477 
1478     // Return size of current map.
mapLength() const1479     int mapLength() const { return m_lastValid + 1; }
1480 
walk(const Input & input)1481     bool walk(const Input &input)
1482     {
1483         m_currentInputs.append(input);
1484 
1485         if (m_modeMapping == m_parent->end())
1486             return false;
1487 
1488         ModeMapping::Iterator it;
1489         if (isValid()) {
1490             it = last()->find(input);
1491             if (it == last()->end())
1492                 return false;
1493         } else {
1494             it = m_modeMapping->find(input);
1495             if (it == m_modeMapping->end())
1496                 return false;
1497         }
1498 
1499         if (!it->value().isEmpty())
1500             m_lastValid = size();
1501         append(it);
1502 
1503         return true;
1504     }
1505 
walk(const Inputs & inputs)1506     bool walk(const Inputs &inputs)
1507     {
1508         foreach (const Input &input, inputs) {
1509             if (!walk(input))
1510                 return false;
1511         }
1512         return true;
1513     }
1514 
1515     // Return current mapped value. Iterator must be valid.
inputs() const1516     const Inputs &inputs() const
1517     {
1518         return at(m_lastValid)->value();
1519     }
1520 
remove()1521     void remove()
1522     {
1523         if (isValid()) {
1524             if (canExtend()) {
1525                 last()->setValue(Inputs());
1526             } else {
1527                 if (size() > 1) {
1528                     while (last()->empty()) {
1529                         at(size() - 2)->erase(last());
1530                         pop_back();
1531                         if (size() == 1 || !last()->value().isEmpty())
1532                             break;
1533                     }
1534                     if (last()->empty() && last()->value().isEmpty())
1535                         m_modeMapping->erase(last());
1536                 } else if (last()->empty() && !last()->value().isEmpty()) {
1537                     m_modeMapping->erase(last());
1538                 }
1539             }
1540         }
1541     }
1542 
setInputs(const Inputs & key,const Inputs & inputs,bool unique=false)1543     void setInputs(const Inputs &key, const Inputs &inputs, bool unique = false)
1544     {
1545         ModeMapping *current = &(*m_parent)[m_mode];
1546         foreach (const Input &input, key)
1547             current = &(*current)[input];
1548         if (!unique || current->value().isEmpty())
1549             current->setValue(inputs);
1550     }
1551 
currentInputs() const1552     const Inputs &currentInputs() const { return m_currentInputs; }
1553 
1554 private:
1555     Mappings *m_parent;
1556     Mappings::Iterator m_modeMapping;
1557     int m_lastValid;
1558     char m_mode;
1559     Inputs m_currentInputs;
1560 };
1561 
1562 // state of current mapping
1563 struct MappingState {
MappingStateFakeVim::Internal::MappingState1564     MappingState()
1565         : noremap(false), silent(false), editBlock(false) {}
MappingStateFakeVim::Internal::MappingState1566     MappingState(bool noremap, bool silent, bool editBlock)
1567         : noremap(noremap), silent(silent), editBlock(editBlock) {}
1568     bool noremap;
1569     bool silent;
1570     bool editBlock;
1571 };
1572 
1573 class FakeVimHandler::Private : public QObject
1574 {
1575     Q_OBJECT
1576 
1577 public:
1578     Private(FakeVimHandler *parent, QWidget *widget);
1579 
1580     EventResult handleEvent(QKeyEvent *ev);
1581     bool wantsOverride(QKeyEvent *ev);
1582     bool parseExCommmand(QString *line, ExCommand *cmd);
1583     bool parseLineRange(QString *line, ExCommand *cmd);
1584     int parseLineAddress(QString *cmd);
1585     void parseRangeCount(const QString &line, Range *range) const;
1586     void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
1587     void handleExCommand(const QString &cmd);
1588 
1589     void installEventFilter();
1590     void removeEventFilter();
1591     void passShortcuts(bool enable);
1592     void setupWidget();
1593     void restoreWidget(int tabSize);
1594 
1595     friend class FakeVimHandler;
1596 
1597     void init();
1598     void focus();
1599 
1600     // Call before any FakeVim processing (import cursor position from editor)
1601     void enterFakeVim();
1602     // Call after any FakeVim processing
1603     // (if needUpdate is true, export cursor position to editor and scroll)
1604     void leaveFakeVim(bool needUpdate = true);
1605 
1606     EventResult handleKey(const Input &input);
1607     EventResult handleDefaultKey(const Input &input);
1608     EventResult handleCurrentMapAsDefault();
1609     void prependInputs(const QVector<Input> &inputs); // Handle inputs.
1610     void prependMapping(const Inputs &inputs); // Handle inputs as mapping.
1611     bool expandCompleteMapping(); // Return false if current mapping is not complete.
1612     bool extendMapping(const Input &input); // Return false if no suitable mappig found.
1613     void endMapping();
1614     bool canHandleMapping();
1615     void clearPendingInput();
1616     void waitForMapping();
1617     EventResult stopWaitForMapping(bool hasInput);
1618     EventResult handleInsertOrReplaceMode(const Input &);
1619     void handleInsertMode(const Input &);
1620     void handleReplaceMode(const Input &);
1621     void finishInsertMode();
1622 
1623     EventResult handleCommandMode(const Input &);
1624 
1625     // return true only if input in current mode and sub-mode was correctly handled
1626     bool handleEscape();
1627     bool handleNoSubMode(const Input &);
1628     bool handleChangeDeleteSubModes(const Input &);
1629     bool handleReplaceSubMode(const Input &);
1630     bool handleFilterSubMode(const Input &);
1631     bool handleRegisterSubMode(const Input &);
1632     bool handleShiftSubMode(const Input &);
1633     bool handleChangeCaseSubMode(const Input &);
1634     bool handleWindowSubMode(const Input &);
1635     bool handleYankSubMode(const Input &);
1636     bool handleZSubMode(const Input &);
1637     bool handleCapitalZSubMode(const Input &);
1638     bool handleMacroRecordSubMode(const Input &);
1639     bool handleMacroExecuteSubMode(const Input &);
1640 
1641     bool handleCount(const Input &); // Handle count for commands (return false if input isn't count).
1642     bool handleMovement(const Input &);
1643 
1644     EventResult handleExMode(const Input &);
1645     EventResult handleSearchSubSubMode(const Input &);
1646     bool handleCommandSubSubMode(const Input &);
1647     void fixSelection(); // Fix selection according to current range, move and command modes.
1648     bool finishSearch();
1649     void finishMovement(const QString &dotCommandMovement = QString());
1650     void resetCommandMode();
1651     void clearCommandMode();
1652     QTextCursor search(const SearchData &sd, int startPos, int count, bool showMessages);
1653     void search(const SearchData &sd, bool showMessages = true);
1654     bool searchNext(bool forward = true);
1655     void searchBalanced(bool forward, QChar needle, QChar other);
1656     void highlightMatches(const QString &needle);
1657     void stopIncrementalFind();
1658     void updateFind(bool isComplete);
1659 
1660     void resetCount();
1661     bool isInputCount(const Input &) const; // Return true if input can be used as count for commands.
mvCount() const1662     int mvCount() const { return qMax(1, g.mvcount); }
opCount() const1663     int opCount() const { return qMax(1, g.opcount); }
count() const1664     int count() const { return mvCount() * opCount(); }
block() const1665     QTextBlock block() const { return m_cursor.block(); }
leftDist() const1666     int leftDist() const { return position() - block().position(); }
rightDist() const1667     int rightDist() const { return block().length() - leftDist() - (isVisualCharMode() ? 0 : 1); }
atBlockStart() const1668     bool atBlockStart() const { return m_cursor.atBlockStart(); }
atBlockEnd() const1669     bool atBlockEnd() const { return m_cursor.atBlockEnd(); }
atEndOfLine() const1670     bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
atDocumentEnd() const1671     bool atDocumentEnd() const { return position() >= lastPositionInDocument(true); }
atDocumentStart() const1672     bool atDocumentStart() const { return m_cursor.atStart(); }
1673 
1674     bool atEmptyLine(const QTextCursor &tc = QTextCursor()) const;
1675     bool atBoundary(bool end, bool simple, bool onlyWords = false,
1676         const QTextCursor &tc = QTextCursor()) const;
1677     bool atWordBoundary(bool end, bool simple, const QTextCursor &tc = QTextCursor()) const;
1678     bool atWordStart(bool simple, const QTextCursor &tc = QTextCursor()) const;
1679     bool atWordEnd(bool simple, const QTextCursor &tc = QTextCursor()) const;
1680     bool isFirstNonBlankOnLine(int pos);
1681 
1682     int lastPositionInDocument(bool ignoreMode = false) const; // Returns last valid position in doc.
1683     int firstPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos
1684     int lastPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos
1685     int lineForPosition(int pos) const;  // 1 based line, 0 based pos
1686     QString lineContents(int line) const; // 1 based line
1687     QString textAt(int from, int to) const;
1688     void setLineContents(int line, const QString &contents); // 1 based line
1689     int blockBoundary(const QString &left, const QString &right,
1690         bool end, int count) const; // end or start position of current code block
1691     int lineNumber(const QTextBlock &block) const;
1692 
1693     QTextBlock nextLine(const QTextBlock &block) const; // following line (respects wrapped parts)
1694     QTextBlock previousLine(const QTextBlock &block) const; // previous line (respects wrapped parts)
1695 
1696     int linesOnScreen() const;
1697     int linesInDocument() const;
1698 
1699     // The following use all zero-based counting.
1700     int cursorLineOnScreen() const;
1701     int cursorLine() const;
1702     int cursorBlockNumber() const; // "." address
1703     int physicalCursorColumn() const; // as stored in the data
1704     int logicalCursorColumn() const; // as visible on screen
1705     int physicalToLogicalColumn(int physical, const QString &text) const;
1706     int logicalToPhysicalColumn(int logical, const QString &text) const;
1707     int windowScrollOffset() const; // return scrolloffset but max half the current window height
1708     Column cursorColumn() const; // as visible on screen
1709     void updateFirstVisibleLine();
1710     int firstVisibleLine() const;
1711     int lastVisibleLine() const;
1712     int lineOnTop(int count = 1) const; // [count]-th line from top reachable without scrolling
1713     int lineOnBottom(int count = 1) const; // [count]-th line from bottom reachable without scrolling
1714     void scrollToLine(int line);
1715     void scrollUp(int count);
scrollDown(int count)1716     void scrollDown(int count) { scrollUp(-count); }
1717     void updateScrollOffset();
1718     void alignViewportToCursor(Qt::AlignmentFlag align, int line = -1,
1719         bool moveToNonBlank = false);
1720 
1721     int lineToBlockNumber(int line) const;
1722 
1723     void setCursorPosition(const CursorPosition &p);
1724     void setCursorPosition(QTextCursor *tc, const CursorPosition &p);
1725 
1726     // Helper functions for indenting/
1727     bool isElectricCharacter(QChar c) const;
1728     void indentSelectedText(QChar lastTyped = QChar());
1729     void indentText(const Range &range, QChar lastTyped = QChar());
1730     void shiftRegionLeft(int repeat = 1);
1731     void shiftRegionRight(int repeat = 1);
1732 
1733     void moveToFirstNonBlankOnLine();
1734     void moveToFirstNonBlankOnLine(QTextCursor *tc);
1735     void moveToFirstNonBlankOnLineVisually();
1736     void moveToNonBlankOnLine(QTextCursor *tc);
1737     void moveToTargetColumn();
1738     void setTargetColumn();
1739     void moveToMatchingParanthesis();
1740     void moveToBoundary(bool simple, bool forward = true);
1741     void moveToNextBoundary(bool end, int count, bool simple, bool forward);
1742     void moveToNextBoundaryStart(int count, bool simple, bool forward = true);
1743     void moveToNextBoundaryEnd(int count, bool simple, bool forward = true);
1744     void moveToBoundaryStart(int count, bool simple, bool forward = true);
1745     void moveToBoundaryEnd(int count, bool simple, bool forward = true);
1746     void moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines);
1747     void moveToNextWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
1748     void moveToNextWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
1749     void moveToWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
1750     void moveToWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
1751 
1752     // Convenience wrappers to reduce line noise.
1753     void moveToStartOfLine();
1754     void moveToStartOfLineVisually();
1755     void moveToEndOfLine();
1756     void moveToEndOfLineVisually();
1757     void moveToEndOfLineVisually(QTextCursor *tc);
1758     void moveBehindEndOfLine();
moveUp(int n=1)1759     void moveUp(int n = 1) { moveDown(-n); }
1760     void moveDown(int n = 1);
moveUpVisually(int n=1)1761     void moveUpVisually(int n = 1) { moveDownVisually(-n); }
1762     void moveDownVisually(int n = 1);
1763     void movePageDown(int count = 1);
movePageUp(int count=1)1764     void movePageUp(int count = 1) { movePageDown(-count); }
dump(const char * msg) const1765     void dump(const char *msg) const {
1766         qDebug() << msg << "POS: " << anchor() << position()
1767             << "EXT: " << m_oldExternalAnchor << m_oldExternalPosition
1768             << "INT: " << m_oldInternalAnchor << m_oldInternalPosition
1769             << "VISUAL: " << g.visualMode;
1770     }
moveRight(int n=1)1771     void moveRight(int n = 1) {
1772         //dump("RIGHT 1");
1773         if (isVisualCharMode()) {
1774             const QTextBlock currentBlock = block();
1775             const int max = currentBlock.position() + currentBlock.length() - 1;
1776             const int pos = position() + n;
1777             setPosition(qMin(pos, max));
1778         } else {
1779             m_cursor.movePosition(Right, KeepAnchor, n);
1780         }
1781         if (atEndOfLine())
1782             emit q->fold(1, false);
1783         //dump("RIGHT 2");
1784     }
moveLeft(int n=1)1785     void moveLeft(int n = 1) {
1786         m_cursor.movePosition(Left, KeepAnchor, n);
1787     }
setAnchor()1788     void setAnchor() {
1789         m_cursor.setPosition(position(), MoveAnchor);
1790     }
setAnchor(int position)1791     void setAnchor(int position) {
1792         m_cursor.setPosition(position, KeepAnchor);
1793     }
setPosition(int position)1794     void setPosition(int position) {
1795         m_cursor.setPosition(position, KeepAnchor);
1796     }
setAnchorAndPosition(int anchor,int position)1797     void setAnchorAndPosition(int anchor, int position) {
1798         m_cursor.setPosition(anchor, MoveAnchor);
1799         m_cursor.setPosition(position, KeepAnchor);
1800     }
1801     // Set cursor in text editor widget.
commitCursor()1802     void commitCursor() {
1803         if (isVisualBlockMode()) {
1804             emit q->requestSetBlockSelection(m_cursor);
1805         } else  {
1806             emit q->requestDisableBlockSelection();
1807             if (editor())
1808                 EDITOR(setTextCursor(m_cursor));
1809         }
1810     }
1811     // Restore cursor from editor widget.
pullCursor()1812     void pullCursor() {
1813         if (isVisualBlockMode())
1814             q->requestBlockSelection(&m_cursor);
1815         else if (editor())
1816             m_cursor = EDITOR(textCursor());
1817     }
1818 
1819     // Values to save when starting FakeVim processing.
1820     int m_firstVisibleLine;
1821     QTextCursor m_cursor;
1822 
moveToPreviousParagraph(int count)1823     bool moveToPreviousParagraph(int count) { return moveToNextParagraph(-count); }
1824     bool moveToNextParagraph(int count);
1825 
1826     bool handleFfTt(const QString &key, bool repeats = false);
1827 
1828     void enterVisualInsertMode(QChar command);
1829     void enterReplaceMode();
1830     void enterInsertMode();
1831     void enterInsertOrReplaceMode(Mode mode);
1832     void enterCommandMode(Mode returnToMode = CommandMode);
1833     void enterExMode(const QString &contents = QString());
1834     void showMessage(MessageLevel level, const QString &msg);
clearMessage()1835     void clearMessage() { showMessage(MessageInfo, QString()); }
1836     void notImplementedYet();
1837     void updateMiniBuffer();
1838     void updateSelection();
1839     void updateHighlights();
1840     void updateCursorShape();
1841     QWidget *editor() const;
document() const1842     QTextDocument *document() const { return EDITOR(document()); }
characterAtCursor() const1843     QChar characterAtCursor() const
1844         { return document()->characterAt(position()); }
1845 
1846     void joinPreviousEditBlock();
1847     void beginEditBlock(bool largeEditBlock = false);
beginLargeEditBlock()1848     void beginLargeEditBlock() { beginEditBlock(true); }
1849     void endEditBlock();
breakEditBlock()1850     void breakEditBlock() { m_buffer->breakEditBlock = true; }
1851 
1852     Q_SLOT void onContentsChanged(int position, int charsRemoved, int charsAdded);
1853     Q_SLOT void onUndoCommandAdded();
1854 
isInsertMode() const1855     bool isInsertMode() const { return g.mode == InsertMode || g.mode == ReplaceMode; }
1856     // Waiting for movement operator.
isOperatorPending() const1857     bool isOperatorPending() const {
1858         return g.submode == ChangeSubMode
1859             || g.submode == DeleteSubMode
1860             || g.submode == FilterSubMode
1861             || g.submode == IndentSubMode
1862             || g.submode == ShiftLeftSubMode
1863             || g.submode == ShiftRightSubMode
1864             || g.submode == InvertCaseSubMode
1865             || g.submode == DownCaseSubMode
1866             || g.submode == UpCaseSubMode
1867             || g.submode == YankSubMode; }
1868 
isVisualMode() const1869     bool isVisualMode() const { return g.visualMode != NoVisualMode; }
isNoVisualMode() const1870     bool isNoVisualMode() const { return g.visualMode == NoVisualMode; }
isVisualCharMode() const1871     bool isVisualCharMode() const { return g.visualMode == VisualCharMode; }
isVisualLineMode() const1872     bool isVisualLineMode() const { return g.visualMode == VisualLineMode; }
isVisualBlockMode() const1873     bool isVisualBlockMode() const { return g.visualMode == VisualBlockMode; }
1874     char currentModeCode() const;
1875     void updateEditor();
1876 
1877     void selectTextObject(bool simple, bool inner);
1878     void selectWordTextObject(bool inner);
1879     void selectWORDTextObject(bool inner);
1880     void selectSentenceTextObject(bool inner);
1881     void selectParagraphTextObject(bool inner);
1882     bool changeNumberTextObject(int count);
1883     // return true only if cursor is in a block delimited with correct characters
1884     bool selectBlockTextObject(bool inner, char left, char right);
1885     bool selectQuotedStringTextObject(bool inner, const QString &quote);
1886 
1887     Q_SLOT void importSelection();
1888     void exportSelection();
1889     void commitInsertState();
1890     void invalidateInsertState();
1891     bool isInsertStateValid() const;
1892     void clearLastInsertion();
1893     void ensureCursorVisible();
1894     void insertInInsertMode(const QString &text);
1895 
1896     // Macro recording
1897     bool startRecording(const Input &input);
1898     void record(const Input &input);
1899     void stopRecording();
1900     bool executeRegister(int reg);
1901 
1902 public:
1903     QTextEdit *m_textedit;
1904     QPlainTextEdit *m_plaintextedit;
1905     bool m_wasReadOnly; // saves read-only state of document
1906 
1907     bool m_inFakeVim; // true if currently processing a key press or a command
1908 
1909     FakeVimHandler *q;
1910     int m_oldExternalPosition; // copy from last event to check for external changes
1911     int m_oldExternalAnchor;
1912     int m_oldInternalPosition; // copy from last event to check for external changes
1913     int m_oldInternalAnchor;
1914     int m_register;
1915     BlockInsertMode m_visualBlockInsert;
1916 
1917     bool m_fakeEnd;
1918     bool m_anchorPastEnd;
1919     bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
1920 
1921     QString m_currentFileName;
1922 
1923     int m_findStartPosition;
1924 
anchor() const1925     int anchor() const { return m_cursor.anchor(); }
position() const1926     int position() const { return m_cursor.position(); }
1927 
1928     struct TransformationData
1929     {
TransformationDataFakeVim::Internal::FakeVimHandler::Private::TransformationData1930         TransformationData(const QString &s, const QVariant &d)
1931             : from(s), extraData(d) {}
1932         QString from;
1933         QString to;
1934         QVariant extraData;
1935     };
1936     typedef void (Private::*Transformation)(TransformationData *td);
1937     void transformText(const Range &range, Transformation transformation,
1938         const QVariant &extraData = QVariant());
1939 
1940     void insertText(QTextCursor &tc, const QString &text);
1941     void insertText(const Register &reg);
1942     void removeText(const Range &range);
1943     void removeTransform(TransformationData *td);
1944 
1945     void invertCase(const Range &range);
1946     void invertCaseTransform(TransformationData *td);
1947 
1948     void upCase(const Range &range);
1949     void upCaseTransform(TransformationData *td);
1950 
1951     void downCase(const Range &range);
1952     void downCaseTransform(TransformationData *td);
1953 
1954     void replaceText(const Range &range, const QString &str);
1955     void replaceByStringTransform(TransformationData *td);
1956     void replaceByCharTransform(TransformationData *td);
1957 
1958     QString selectText(const Range &range) const;
1959     void setCurrentRange(const Range &range);
currentRange() const1960     Range currentRange() const { return Range(position(), anchor(), g.rangemode); }
1961 
1962     void yankText(const Range &range, int toregister);
1963 
1964     void pasteText(bool afterCursor);
1965 
1966     void joinLines(int count, bool preserveSpace = false);
1967 
1968     void insertNewLine();
1969 
1970     bool handleInsertInEditor(const Input &input);
1971     bool passEventToEditor(QEvent &event); // Pass event to editor widget without filtering. Returns true if event was processed.
1972 
1973     // undo handling
revision() const1974     int revision() const { return document()->availableUndoSteps(); }
1975     void undoRedo(bool undo);
1976     void undo();
1977     void redo();
1978     void pushUndoState(bool overwrite = true);
1979 
1980     // extra data for '.'
1981     void replay(const QString &text, int repeat = 1);
setDotCommand(const QString & cmd)1982     void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
setDotCommand(const QString & cmd,int n)1983     void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
1984     QString visualDotCommand() const;
1985 
1986     // visual modes
1987     void toggleVisualMode(VisualMode visualMode);
1988     void leaveVisualMode();
1989 
1990     // marks
1991     Mark mark(QChar code) const;
1992     void setMark(QChar code, CursorPosition position);
1993     // jump to valid mark return true if mark is valid and local
1994     bool jumpToMark(QChar mark, bool backTickMode);
1995     // update marks on undo/redo
1996     void updateMarks(const Marks &newMarks);
markLessPosition() const1997     CursorPosition markLessPosition() const { return mark(QLatin1Char('<')).position(document()); }
markGreaterPosition() const1998     CursorPosition markGreaterPosition() const { return mark(QLatin1Char('>')).position(document()); }
1999 
2000     // vi style configuration
config(int code) const2001     QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
hasConfig(int code) const2002     bool hasConfig(int code) const { return config(code).toBool(); }
hasConfig(int code,const char * value) const2003     bool hasConfig(int code, const char *value) const // FIXME
2004         { return config(code).toString().contains(_(value)); }
2005 
2006     int m_targetColumn; // -1 if past end of line
2007     int m_visualTargetColumn; // 'l' can move past eol in visual mode only
2008     int m_targetColumnWrapped; // column in current part of wrapped line
2009 
2010     // auto-indent
2011     QString tabExpand(int len) const;
2012     Column indentation(const QString &line) const;
2013     void insertAutomaticIndentation(bool goingDown, bool forceAutoIndent = false);
2014     // number of autoindented characters
2015     void handleStartOfLine();
2016 
2017     // register handling
2018     QString registerContents(int reg) const;
2019     void setRegister(int reg, const QString &contents, RangeMode mode);
2020     RangeMode registerRangeMode(int reg) const;
2021     void getRegisterType(int reg, bool *isClipboard, bool *isSelection) const;
2022 
2023     void recordJump(int position = -1);
2024     void jump(int distance);
2025 
2026     QList<QTextEdit::ExtraSelection> m_extraSelections;
2027     QTextCursor m_searchCursor;
2028     int m_searchStartPosition;
2029     int m_searchFromScreenLine;
2030     QString m_highlighted; // currently highlighted text
2031 
2032     bool handleExCommandHelper(ExCommand &cmd); // Returns success.
2033     bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
2034     bool handleExBangCommand(const ExCommand &cmd);
2035     bool handleExYankDeleteCommand(const ExCommand &cmd);
2036     bool handleExChangeCommand(const ExCommand &cmd);
2037     bool handleExMoveCommand(const ExCommand &cmd);
2038     bool handleExJoinCommand(const ExCommand &cmd);
2039     bool handleExGotoCommand(const ExCommand &cmd);
2040     bool handleExHistoryCommand(const ExCommand &cmd);
2041     bool handleExRegisterCommand(const ExCommand &cmd);
2042     bool handleExMapCommand(const ExCommand &cmd);
2043     bool handleExNohlsearchCommand(const ExCommand &cmd);
2044     bool handleExNormalCommand(const ExCommand &cmd);
2045     bool handleExReadCommand(const ExCommand &cmd);
2046     bool handleExUndoRedoCommand(const ExCommand &cmd);
2047     bool handleExSetCommand(const ExCommand &cmd);
2048     bool handleExShiftCommand(const ExCommand &cmd);
2049     bool handleExSourceCommand(const ExCommand &cmd);
2050     bool handleExSubstituteCommand(const ExCommand &cmd);
2051     bool handleExWriteCommand(const ExCommand &cmd);
2052     bool handleExEchoCommand(const ExCommand &cmd);
2053 
2054     void timerEvent(QTimerEvent *ev);
2055 
2056     void setupCharClass();
2057     int charClass(QChar c, bool simple) const;
2058     signed char m_charClass[256];
2059 
2060     int m_ctrlVAccumulator;
2061     int m_ctrlVLength;
2062     int m_ctrlVBase;
2063 
2064     void miniBufferTextEdited(const QString &text, int cursorPos, int anchorPos);
2065 
2066     // Data shared among editors with same document.
2067     struct BufferData
2068     {
BufferDataFakeVim::Internal::FakeVimHandler::Private::BufferData2069         BufferData()
2070             : lastRevision(0)
2071             , editBlockLevel(0)
2072             , breakEditBlock(false)
2073             , lastVisualMode(NoVisualMode)
2074             , lastVisualModeInverted(false)
2075         {}
2076 
2077         QStack<State> undo;
2078         QStack<State> redo;
2079         State undoState;
2080         int lastRevision;
2081 
2082         int editBlockLevel; // current level of edit blocks
2083         bool breakEditBlock; // if true, joinPreviousEditBlock() starts new edit block
2084 
2085         QStack<CursorPosition> jumpListUndo;
2086         QStack<CursorPosition> jumpListRedo;
2087         CursorPosition lastChangePosition;
2088 
2089         VisualMode lastVisualMode;
2090         bool lastVisualModeInverted;
2091 
2092         Marks marks;
2093 
2094         // Insert state to get last inserted text.
2095         struct InsertState {
2096             int pos1;
2097             int pos2;
2098             int backspaces;
2099             int deletes;
2100             QSet<int> spaces;
2101             bool insertingSpaces;
2102             QString textBeforeCursor;
2103             bool newLineBefore;
2104             bool newLineAfter;
2105         } insertState;
2106 
2107         QString lastInsertion;
2108     };
2109 
2110     typedef QSharedPointer<BufferData> BufferDataPtr;
2111     void pullOrCreateBufferData();
2112     BufferDataPtr m_buffer;
2113 
2114     // Data shared among all editors.
2115     static struct GlobalData
2116     {
GlobalDataFakeVim::Internal::FakeVimHandler::Private::GlobalData2117         GlobalData()
2118             : passing(false)
2119             , mode(CommandMode)
2120             , submode(NoSubMode)
2121             , subsubmode(NoSubSubMode)
2122             , visualMode(NoVisualMode)
2123             , mvcount(0)
2124             , opcount(0)
2125             , movetype(MoveInclusive)
2126             , rangemode(RangeCharMode)
2127             , gflag(false)
2128             , mappings()
2129             , currentMap(&mappings)
2130             , inputTimer(-1)
2131             , mapDepth(0)
2132             , currentMessageLevel(MessageInfo)
2133             , lastSearchForward(false)
2134             , highlightsCleared(false)
2135             , findPending(false)
2136             , returnToMode(CommandMode)
2137             , currentRegister(0)
2138             , lastExecutedRegister(0)
2139         {
2140             commandBuffer.setPrompt(QLatin1Char(':'));
2141         }
2142 
2143         // Current state.
2144         bool passing; // let the core see the next event
2145         Mode mode;
2146         SubMode submode;
2147         SubSubMode subsubmode;
2148         Input subsubdata;
2149         VisualMode visualMode;
2150 
2151         // [count] for current command, 0 if no [count] available
2152         int mvcount;
2153         int opcount;
2154 
2155         MoveType movetype;
2156         RangeMode rangemode;
2157         bool gflag;  // whether current command started with 'g'
2158 
2159         // Extra data for ';'.
2160         Input semicolonType;  // 'f', 'F', 't', 'T'
2161         QString semicolonKey;
2162 
2163         // Repetition.
2164         QString dotCommand;
2165 
2166         QHash<int, Register> registers;
2167 
2168         // All mappings.
2169         Mappings mappings;
2170 
2171         // Input.
2172         QList<Input> pendingInput;
2173         MappingsIterator currentMap;
2174         int inputTimer;
2175         QStack<MappingState> mapStates;
2176         int mapDepth;
2177 
2178         // Command line buffers.
2179         CommandBuffer commandBuffer;
2180         CommandBuffer searchBuffer;
2181 
2182         // Current mini buffer message.
2183         QString currentMessage;
2184         MessageLevel currentMessageLevel;
2185         QString currentCommand;
2186 
2187         // Search state.
2188         QString lastSearch; // last search expression as entered by user
2189         QString lastNeedle; // last search expression translated with vimPatternToQtPattern()
2190         bool lastSearchForward; // last search command was '/' or '*'
2191         bool highlightsCleared; // ':nohlsearch' command is active until next search
2192         bool findPending; // currently searching using external tool (until editor is focused again)
2193 
2194         // Last substitution command.
2195         QString lastSubstituteFlags;
2196         QString lastSubstitutePattern;
2197         QString lastSubstituteReplacement;
2198 
2199         // Global marks.
2200         Marks marks;
2201 
2202         // Return to insert/replace mode after single command (<C-O>).
2203         Mode returnToMode;
2204 
2205         // Currently recorded macro (not recording if null string).
2206         QString recording;
2207         int currentRegister;
2208         int lastExecutedRegister;
2209     } g;
2210 };
2211 
2212 FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
2213 
Private(FakeVimHandler * parent,QWidget * widget)2214 FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
2215 {
2216     q = parent;
2217     m_textedit = qobject_cast<QTextEdit *>(widget);
2218     m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
2219 
2220     init();
2221 
2222     if (editor()) {
2223         connect(EDITOR(document()), SIGNAL(contentsChange(int,int,int)),
2224                 SLOT(onContentsChanged(int,int,int)));
2225         connect(EDITOR(document()), SIGNAL(undoCommandAdded()), SLOT(onUndoCommandAdded()));
2226         m_buffer->lastRevision = revision();
2227     }
2228 }
2229 
init()2230 void FakeVimHandler::Private::init()
2231 {
2232     m_inFakeVim = false;
2233     m_findStartPosition = -1;
2234     m_visualBlockInsert = NoneBlockInsertMode;
2235     m_fakeEnd = false;
2236     m_positionPastEnd = false;
2237     m_anchorPastEnd = false;
2238     m_register = '"';
2239     m_targetColumn = 0;
2240     m_visualTargetColumn = 0;
2241     m_targetColumnWrapped = 0;
2242     m_oldInternalAnchor = -1;
2243     m_oldInternalPosition = -1;
2244     m_oldExternalAnchor = -1;
2245     m_oldExternalPosition = -1;
2246     m_searchStartPosition = 0;
2247     m_searchFromScreenLine = 0;
2248     m_firstVisibleLine = 0;
2249     m_ctrlVAccumulator = 0;
2250     m_ctrlVLength = 0;
2251     m_ctrlVBase = 0;
2252 
2253     pullOrCreateBufferData();
2254     setupCharClass();
2255 }
2256 
focus()2257 void FakeVimHandler::Private::focus()
2258 {
2259     enterFakeVim();
2260 
2261     stopIncrementalFind();
2262     if (!isInsertMode()) {
2263         if (g.subsubmode == SearchSubSubMode) {
2264             setPosition(m_searchStartPosition);
2265             scrollToLine(m_searchFromScreenLine);
2266             setTargetColumn();
2267             setAnchor();
2268             commitCursor();
2269         } else if (g.submode != NoSubMode || g.mode == ExMode) {
2270             leaveVisualMode();
2271             setPosition(qMin(position(), anchor()));
2272             setTargetColumn();
2273             setAnchor();
2274             commitCursor();
2275         }
2276 
2277         bool exitCommandLine = (g.subsubmode == SearchSubSubMode || g.mode == ExMode);
2278         resetCommandMode();
2279         if (exitCommandLine)
2280             updateMiniBuffer();
2281     }
2282     updateCursorShape();
2283     if (g.mode != CommandMode)
2284         updateMiniBuffer();
2285     updateHighlights();
2286 
2287     leaveFakeVim(false);
2288 }
2289 
enterFakeVim()2290 void FakeVimHandler::Private::enterFakeVim()
2291 {
2292     QTC_ASSERT(!m_inFakeVim, qDebug() << "enterFakeVim() shouldn't be called recursively!"; return);
2293 
2294     pullOrCreateBufferData();
2295 
2296     pullCursor();
2297     if (m_cursor.isNull())
2298         m_cursor = QTextCursor(document());
2299 
2300     m_inFakeVim = true;
2301 
2302     removeEventFilter();
2303 
2304     updateFirstVisibleLine();
2305     importSelection();
2306 
2307     // Position changed externally, e.g. by code completion.
2308     if (position() != m_oldInternalPosition) {
2309         // record external jump to different line
2310         if (m_oldInternalPosition != -1 && lineForPosition(m_oldInternalPosition) != lineForPosition(position()))
2311             recordJump(m_oldInternalPosition);
2312         setTargetColumn();
2313         if (atEndOfLine() && !isVisualMode() && !isInsertMode())
2314             moveLeft();
2315     }
2316 
2317     if (m_fakeEnd)
2318         moveRight();
2319 }
2320 
leaveFakeVim(bool needUpdate)2321 void FakeVimHandler::Private::leaveFakeVim(bool needUpdate)
2322 {
2323     QTC_ASSERT(m_inFakeVim, qDebug() << "enterFakeVim() not called before leaveFakeVim()!"; return);
2324 
2325     // The command might have destroyed the editor.
2326     if (m_textedit || m_plaintextedit) {
2327         // We fake vi-style end-of-line behaviour
2328         m_fakeEnd = atEndOfLine() && g.mode == CommandMode && !isVisualBlockMode()
2329             && !isVisualCharMode();
2330 
2331         //QTC_ASSERT(g.mode == InsertMode || g.mode == ReplaceMode
2332         //        || !atBlockEnd() || block().length() <= 1,
2333         //    qDebug() << "Cursor at EOL after key handler");
2334         if (m_fakeEnd)
2335             moveLeft();
2336 
2337         if (hasConfig(ConfigShowMarks))
2338             updateSelection();
2339 
2340         exportSelection();
2341         updateCursorShape();
2342 
2343         if (needUpdate) {
2344             commitCursor();
2345 
2346             // Move cursor line to middle of screen if it's not visible.
2347             const int line = cursorLine();
2348             if (line < firstVisibleLine() || line > firstVisibleLine() + linesOnScreen())
2349                 scrollToLine(qMax(0, line - linesOnScreen() / 2));
2350             else
2351                 scrollToLine(firstVisibleLine());
2352             updateScrollOffset();
2353         }
2354 
2355         installEventFilter();
2356     }
2357 
2358     m_inFakeVim = false;
2359 }
2360 
wantsOverride(QKeyEvent * ev)2361 bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
2362 {
2363     const int key = ev->key();
2364     const Qt::KeyboardModifiers mods = ev->modifiers();
2365     KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << g.passing);
2366 
2367     if (key == Key_Escape) {
2368         if (g.subsubmode == SearchSubSubMode)
2369             return true;
2370         // Not sure this feels good. People often hit Esc several times.
2371         if (isNoVisualMode()
2372                 && g.mode == CommandMode
2373                 && g.submode == NoSubMode
2374                 && g.currentCommand.isEmpty()
2375                 && g.returnToMode == CommandMode)
2376             return false;
2377         return true;
2378     }
2379 
2380     // We are interested in overriding most Ctrl key combinations.
2381     if (isOnlyControlModifier(mods)
2382             && !config(ConfigPassControlKey).toBool()
2383             && ((key >= Key_A && key <= Key_Z && key != Key_K)
2384                 || key == Key_BracketLeft || key == Key_BracketRight)) {
2385         // Ctrl-K is special as it is the Core's default notion of Locator
2386         if (g.passing) {
2387             KEY_DEBUG(" PASSING CTRL KEY");
2388             // We get called twice on the same key
2389             //g.passing = false;
2390             return false;
2391         }
2392         KEY_DEBUG(" NOT PASSING CTRL KEY");
2393         //updateMiniBuffer();
2394         return true;
2395     }
2396 
2397     // Let other shortcuts trigger.
2398     return false;
2399 }
2400 
handleEvent(QKeyEvent * ev)2401 EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
2402 {
2403     const int key = ev->key();
2404     const Qt::KeyboardModifiers mods = ev->modifiers();
2405 
2406     if (key == Key_Shift || key == Key_Alt || key == Key_Control
2407             || key == Key_AltGr || key == Key_Meta)
2408     {
2409         KEY_DEBUG("PLAIN MODIFIER");
2410         return EventUnhandled;
2411     }
2412 
2413     if (g.passing) {
2414         passShortcuts(false);
2415         KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
2416         //if (input.is(',')) { // use ',,' to leave, too.
2417         //    qDebug() << "FINISHED...";
2418         //    return EventHandled;
2419         //}
2420         g.passing = false;
2421         updateMiniBuffer();
2422         KEY_DEBUG("   PASS TO CORE");
2423         return EventPassedToCore;
2424     }
2425 
2426 #ifndef FAKEVIM_STANDALONE
2427     bool inSnippetMode = false;
2428     QMetaObject::invokeMethod(editor(),
2429         "inSnippetMode", Q_ARG(bool *, &inSnippetMode));
2430 
2431     if (inSnippetMode)
2432         return EventPassedToCore;
2433 #endif
2434 
2435     // Fake "End of line"
2436     //m_tc = m_cursor;
2437 
2438     //bool hasBlock = false;
2439     //emit q->requestHasBlockSelection(&hasBlock);
2440     //qDebug() << "IMPORT BLOCK 2:" << hasBlock;
2441 
2442     //if (0 && hasBlock) {
2443     //    (pos > anc) ? --pos : --anc;
2444 
2445     //if ((mods & RealControlModifier) != 0) {
2446     //    if (key >= Key_A && key <= Key_Z)
2447     //        key = shift(key); // make it lower case
2448     //    key = control(key);
2449     //} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
2450     //    key = shift(key);
2451     //}
2452 
2453     //QTC_ASSERT(g.mode == InsertMode || g.mode == ReplaceMode
2454     //        || !atBlockEnd() || block().length() <= 1,
2455     //    qDebug() << "Cursor at EOL before key handler");
2456 
2457     enterFakeVim();
2458     EventResult result = handleKey(Input(key, mods, ev->text()));
2459     leaveFakeVim(result == EventHandled);
2460 
2461     return result;
2462 }
2463 
installEventFilter()2464 void FakeVimHandler::Private::installEventFilter()
2465 {
2466     EDITOR(viewport()->installEventFilter(q));
2467     EDITOR(installEventFilter(q));
2468 }
2469 
removeEventFilter()2470 void FakeVimHandler::Private::removeEventFilter()
2471 {
2472     EDITOR(viewport()->removeEventFilter(q));
2473     EDITOR(removeEventFilter(q));
2474 }
2475 
setupWidget()2476 void FakeVimHandler::Private::setupWidget()
2477 {
2478     enterFakeVim();
2479 
2480     resetCommandMode();
2481     m_wasReadOnly = EDITOR(isReadOnly());
2482 
2483     updateEditor();
2484     importSelection();
2485     updateMiniBuffer();
2486     updateCursorShape();
2487 
2488     recordJump();
2489     setTargetColumn();
2490     if (atEndOfLine() && !isVisualMode() && !isInsertMode())
2491         moveLeft();
2492 
2493     leaveFakeVim();
2494 }
2495 
exportSelection()2496 void FakeVimHandler::Private::exportSelection()
2497 {
2498     int pos = position();
2499     int anc = isVisualMode() ? anchor() : position();
2500 
2501     m_oldInternalPosition = pos;
2502     m_oldInternalAnchor = anc;
2503 
2504     if (isVisualMode()) {
2505         if (g.visualMode == VisualBlockMode) {
2506             const int col1 = anc - document()->findBlock(anc).position();
2507             const int col2 = pos - document()->findBlock(pos).position();
2508             if (col1 > col2)
2509                 ++anc;
2510             else if (!atBlockEnd())
2511                 ++pos;
2512             // FIXME: After '$' command (i.e. m_visualTargetColumn == -1), end of selected lines
2513             //        should be selected.
2514             setAnchorAndPosition(anc, pos);
2515             commitCursor();
2516         } else if (g.visualMode == VisualLineMode) {
2517             const int posLine = lineForPosition(pos);
2518             const int ancLine = lineForPosition(anc);
2519             if (anc < pos) {
2520                 pos = lastPositionInLine(posLine);
2521                 anc = firstPositionInLine(ancLine);
2522             } else {
2523                 pos = firstPositionInLine(posLine);
2524                 anc = lastPositionInLine(ancLine) + 1;
2525             }
2526             // putting cursor on folded line will unfold the line, so move the cursor a bit
2527             if (!document()->findBlock(pos).isVisible())
2528                 ++pos;
2529             setAnchorAndPosition(anc, pos);
2530         } else if (g.visualMode == VisualCharMode) {
2531             if (anc > pos)
2532                 ++anc;
2533         } else {
2534             QTC_CHECK(false);
2535         }
2536 
2537         setAnchorAndPosition(anc, pos);
2538 
2539         setMark(QLatin1Char('<'), markLessPosition());
2540         setMark(QLatin1Char('>'), markGreaterPosition());
2541     } else {
2542         if (g.subsubmode == SearchSubSubMode && !m_searchCursor.isNull())
2543             m_cursor = m_searchCursor;
2544         else
2545             setAnchorAndPosition(pos, pos);
2546     }
2547     m_oldExternalPosition = position();
2548     m_oldExternalAnchor = anchor();
2549 }
2550 
commitInsertState()2551 void FakeVimHandler::Private::commitInsertState()
2552 {
2553     if (!isInsertStateValid())
2554         return;
2555 
2556     QString &lastInsertion = m_buffer->lastInsertion;
2557     BufferData::InsertState &insertState = m_buffer->insertState;
2558 
2559     // Get raw inserted text.
2560     lastInsertion = textAt(insertState.pos1, insertState.pos2);
2561 
2562     // Escape special characters and spaces inserted by user (not by auto-indentation).
2563     for (int i = lastInsertion.size() - 1; i >= 0; --i) {
2564         const int pos = insertState.pos1 + i;
2565         const ushort c = document()->characterAt(pos).unicode();
2566         if (c == '<')
2567             lastInsertion.replace(i, 1, _("<LT>"));
2568         else if ((c == ' ' || c == '\t') && insertState.spaces.contains(pos))
2569             lastInsertion.replace(i, 1, _(c == ' ' ? "<SPACE>" : "<TAB>"));
2570     }
2571 
2572     // Remove unnecessary backspaces.
2573     while (insertState.backspaces > 0 && !lastInsertion.isEmpty() && lastInsertion[0].isSpace())
2574         --insertState.backspaces;
2575 
2576     // backspaces in front of inserted text
2577     lastInsertion.prepend(QString(_("<BS>")).repeated(insertState.backspaces));
2578     // deletes after inserted text
2579     lastInsertion.prepend(QString(_("<DELETE>")).repeated(insertState.deletes));
2580 
2581     // Remove indentation.
2582     lastInsertion.replace(QRegExp(_("(^|\n)[\\t ]+")), _("\\1"));
2583 }
2584 
invalidateInsertState()2585 void FakeVimHandler::Private::invalidateInsertState()
2586 {
2587     m_oldInternalPosition = position();
2588     BufferData::InsertState &insertState = m_buffer->insertState;
2589     insertState.pos1 = -1;
2590     insertState.pos2 = m_oldInternalPosition;
2591     insertState.backspaces = 0;
2592     insertState.deletes = 0;
2593     insertState.spaces.clear();
2594     insertState.insertingSpaces = false;
2595     insertState.textBeforeCursor = textAt(document()->findBlock(m_oldInternalPosition).position(),
2596                                             m_oldInternalPosition);
2597     insertState.newLineBefore = false;
2598     insertState.newLineAfter = false;
2599 }
2600 
isInsertStateValid() const2601 bool FakeVimHandler::Private::isInsertStateValid() const
2602 {
2603     return m_buffer->insertState.pos1 != -1;
2604 }
2605 
clearLastInsertion()2606 void FakeVimHandler::Private::clearLastInsertion()
2607 {
2608     invalidateInsertState();
2609     m_buffer->lastInsertion.clear();
2610     m_buffer->insertState.pos1 = m_buffer->insertState.pos2;
2611 }
2612 
ensureCursorVisible()2613 void FakeVimHandler::Private::ensureCursorVisible()
2614 {
2615     int pos = position();
2616     int anc = isVisualMode() ? anchor() : position();
2617 
2618     // fix selection so it is outside folded block
2619     int start = qMin(pos, anc);
2620     int end = qMax(pos, anc) + 1;
2621     QTextBlock block = document()->findBlock(start);
2622     QTextBlock block2 = document()->findBlock(end);
2623     if (!block.isVisible() || !block2.isVisible()) {
2624         // FIXME: Moving cursor left/right or unfolding block immediately after block is folded
2625         //        should restore cursor position inside block.
2626         // Changing cursor position after folding is not Vim behavior so at least record the jump.
2627         if (block.isValid() && !block.isVisible())
2628             recordJump();
2629 
2630         pos = start;
2631         while (block.isValid() && !block.isVisible())
2632             block = block.previous();
2633         if (block.isValid())
2634             pos = block.position() + qMin(m_targetColumn, block.length() - 2);
2635 
2636         if (isVisualMode()) {
2637             anc = end;
2638             while (block2.isValid() && !block2.isVisible()) {
2639                 anc = block2.position() + block2.length() - 2;
2640                 block2 = block2.next();
2641             }
2642         }
2643 
2644         setAnchorAndPosition(anc, pos);
2645     }
2646 }
2647 
importSelection()2648 void FakeVimHandler::Private::importSelection()
2649 {
2650     if (position() == m_oldExternalPosition
2651             && anchor() == m_oldExternalAnchor) {
2652         // Undo drawing correction.
2653         setAnchorAndPosition(m_oldInternalAnchor, m_oldInternalPosition);
2654     } else {
2655         // Import new selection.
2656         Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
2657         if (m_cursor.hasSelection()) {
2658             if (mods & HostOsInfo::controlModifier())
2659                 g.visualMode = VisualBlockMode;
2660             else if (mods & Qt::AltModifier)
2661                 g.visualMode = VisualBlockMode;
2662             else if (mods & Qt::ShiftModifier)
2663                 g.visualMode = VisualLineMode;
2664             else
2665                 g.visualMode = VisualCharMode;
2666             m_buffer->lastVisualMode = g.visualMode;
2667         } else {
2668             g.visualMode = NoVisualMode;
2669         }
2670     }
2671 }
2672 
updateEditor()2673 void FakeVimHandler::Private::updateEditor()
2674 {
2675     const int charWidth = QFontMetrics(EDITOR(font())).width(QLatin1Char(' '));
2676     EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
2677     setupCharClass();
2678 }
2679 
restoreWidget(int tabSize)2680 void FakeVimHandler::Private::restoreWidget(int tabSize)
2681 {
2682     //clearMessage();
2683     //updateMiniBuffer();
2684     //EDITOR(removeEventFilter(q));
2685     //EDITOR(setReadOnly(m_wasReadOnly));
2686     const int charWidth = QFontMetrics(EDITOR(font())).width(QLatin1Char(' '));
2687     EDITOR(setTabStopWidth(charWidth * tabSize));
2688     g.visualMode = NoVisualMode;
2689     // Force "ordinary" cursor.
2690     EDITOR(setOverwriteMode(false));
2691     updateSelection();
2692     updateHighlights();
2693 }
2694 
handleKey(const Input & input)2695 EventResult FakeVimHandler::Private::handleKey(const Input &input)
2696 {
2697     KEY_DEBUG("HANDLE INPUT: " << input << " MODE: " << mode);
2698 
2699     bool hasInput = input.isValid();
2700 
2701     // Waiting on input to complete mapping?
2702     EventResult r = stopWaitForMapping(hasInput);
2703 
2704     if (hasInput) {
2705         record(input);
2706         g.pendingInput.append(input);
2707     }
2708 
2709     // Process pending input.
2710     // Note: Pending input is global state and can be extended by:
2711     //         1. handling a user input (though handleKey() is not called recursively),
2712     //         2. expanding a user mapping or
2713     //         3. executing a register.
2714     while (!g.pendingInput.isEmpty() && r == EventHandled) {
2715         const Input in = g.pendingInput.takeFirst();
2716 
2717         // invalid input is used to pop mapping state
2718         if (!in.isValid()) {
2719             endMapping();
2720         } else {
2721             // Handle user mapping.
2722             if (canHandleMapping()) {
2723                 if (extendMapping(in)) {
2724                     if (!hasInput || !g.currentMap.canExtend())
2725                         expandCompleteMapping();
2726                 } else if (!expandCompleteMapping()) {
2727                     r = handleCurrentMapAsDefault();
2728                 }
2729             } else {
2730                 r = handleDefaultKey(in);
2731             }
2732         }
2733     }
2734 
2735     if (g.currentMap.canExtend()) {
2736         waitForMapping();
2737         return EventHandled;
2738     }
2739 
2740     if (r != EventHandled)
2741         clearPendingInput();
2742 
2743     return r;
2744 }
2745 
handleDefaultKey(const Input & input)2746 EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input)
2747 {
2748     if (input == Nop)
2749         return EventHandled;
2750     else if (g.subsubmode == SearchSubSubMode)
2751         return handleSearchSubSubMode(input);
2752     else if (g.mode == CommandMode)
2753         return handleCommandMode(input);
2754     else if (g.mode == InsertMode || g.mode == ReplaceMode)
2755         return handleInsertOrReplaceMode(input);
2756     else if (g.mode == ExMode)
2757         return handleExMode(input);
2758     return EventUnhandled;
2759 }
2760 
handleCurrentMapAsDefault()2761 EventResult FakeVimHandler::Private::handleCurrentMapAsDefault()
2762 {
2763     // If mapping has failed take the first input from it and try default command.
2764     const Inputs &inputs = g.currentMap.currentInputs();
2765 
2766     Input in = inputs.front();
2767     if (inputs.size() > 1)
2768         prependInputs(inputs.mid(1));
2769     g.currentMap.reset();
2770 
2771     return handleDefaultKey(in);
2772 }
2773 
prependInputs(const QVector<Input> & inputs)2774 void FakeVimHandler::Private::prependInputs(const QVector<Input> &inputs)
2775 {
2776     for (int i = inputs.size() - 1; i >= 0; --i)
2777         g.pendingInput.prepend(inputs[i]);
2778 }
2779 
prependMapping(const Inputs & inputs)2780 void FakeVimHandler::Private::prependMapping(const Inputs &inputs)
2781 {
2782     // FIXME: Implement Vim option maxmapdepth (default value is 1000).
2783     if (g.mapDepth >= 1000) {
2784         const int i = qMax(0, g.pendingInput.lastIndexOf(Input()));
2785         QList<Input> inputs = g.pendingInput.mid(i);
2786         clearPendingInput();
2787         g.pendingInput.append(inputs);
2788         showMessage(MessageError, tr("Recursive mapping"));
2789         updateMiniBuffer();
2790         return;
2791     }
2792 
2793     ++g.mapDepth;
2794     g.pendingInput.prepend(Input());
2795     prependInputs(inputs);
2796     g.commandBuffer.setHistoryAutoSave(false);
2797 
2798     // start new edit block (undo/redo) only if necessary
2799     bool editBlock = m_buffer->editBlockLevel == 0 && !(isInsertMode() && isInsertStateValid());
2800     if (editBlock)
2801         beginLargeEditBlock();
2802     g.mapStates << MappingState(inputs.noremap(), inputs.silent(), editBlock);
2803 }
2804 
expandCompleteMapping()2805 bool FakeVimHandler::Private::expandCompleteMapping()
2806 {
2807     if (!g.currentMap.isComplete())
2808         return false;
2809 
2810     const Inputs &inputs = g.currentMap.inputs();
2811     int usedInputs = g.currentMap.mapLength();
2812     prependInputs(g.currentMap.currentInputs().mid(usedInputs));
2813     prependMapping(inputs);
2814     g.currentMap.reset();
2815 
2816     return true;
2817 }
2818 
extendMapping(const Input & input)2819 bool FakeVimHandler::Private::extendMapping(const Input &input)
2820 {
2821     if (!g.currentMap.isValid())
2822         g.currentMap.reset(currentModeCode());
2823     return g.currentMap.walk(input);
2824 }
2825 
endMapping()2826 void FakeVimHandler::Private::endMapping()
2827 {
2828     if (!g.currentMap.canExtend())
2829         --g.mapDepth;
2830     if (g.mapStates.isEmpty())
2831         return;
2832     if (g.mapStates.last().editBlock)
2833         endEditBlock();
2834     g.mapStates.pop_back();
2835     if (g.mapStates.isEmpty())
2836         g.commandBuffer.setHistoryAutoSave(true);
2837     updateMiniBuffer();
2838 }
2839 
canHandleMapping()2840 bool FakeVimHandler::Private::canHandleMapping()
2841 {
2842     // Don't handle user mapping in sub-modes that cannot be followed by movement and in "noremap".
2843     return g.subsubmode == NoSubSubMode
2844         && g.submode != RegisterSubMode
2845         && g.submode != WindowSubMode
2846         && g.submode != ZSubMode
2847         && g.submode != CapitalZSubMode
2848         && g.submode != ReplaceSubMode
2849         && g.submode != MacroRecordSubMode
2850         && g.submode != MacroExecuteSubMode
2851         && (g.mapStates.isEmpty() || !g.mapStates.last().noremap);
2852 }
2853 
clearPendingInput()2854 void FakeVimHandler::Private::clearPendingInput()
2855 {
2856     // Clear pending input on interrupt or bad mapping.
2857     g.pendingInput.clear();
2858     g.mapStates.clear();
2859     g.mapDepth = 0;
2860 
2861     // Clear all started edit blocks.
2862     while (m_buffer->editBlockLevel > 0)
2863         endEditBlock();
2864 }
2865 
waitForMapping()2866 void FakeVimHandler::Private::waitForMapping()
2867 {
2868     g.currentCommand.clear();
2869     foreach (const Input &input, g.currentMap.currentInputs())
2870         g.currentCommand.append(input.toString());
2871     updateMiniBuffer();
2872 
2873     // wait for user to press any key or trigger complete mapping after interval
2874     g.inputTimer = startTimer(1000);
2875 }
2876 
stopWaitForMapping(bool hasInput)2877 EventResult FakeVimHandler::Private::stopWaitForMapping(bool hasInput)
2878 {
2879     if (g.inputTimer != -1) {
2880         killTimer(g.inputTimer);
2881         g.inputTimer = -1;
2882         g.currentCommand.clear();
2883         if (!hasInput && !expandCompleteMapping()) {
2884             // Cannot complete mapping so handle the first input from it as default command.
2885             return handleCurrentMapAsDefault();
2886         }
2887     }
2888 
2889     return EventHandled;
2890 }
2891 
timerEvent(QTimerEvent * ev)2892 void FakeVimHandler::Private::timerEvent(QTimerEvent *ev)
2893 {
2894     if (ev->timerId() == g.inputTimer) {
2895         enterFakeVim();
2896         EventResult result = handleKey(Input());
2897         leaveFakeVim(result == EventHandled);
2898     }
2899 }
2900 
stopIncrementalFind()2901 void FakeVimHandler::Private::stopIncrementalFind()
2902 {
2903     if (g.findPending) {
2904         g.findPending = false;
2905         setAnchorAndPosition(m_findStartPosition, m_cursor.selectionStart());
2906         finishMovement();
2907         setAnchor();
2908     }
2909 }
2910 
updateFind(bool isComplete)2911 void FakeVimHandler::Private::updateFind(bool isComplete)
2912 {
2913     if (!isComplete && !hasConfig(ConfigIncSearch))
2914         return;
2915 
2916     g.currentMessage.clear();
2917 
2918     const QString &needle = g.searchBuffer.contents();
2919     if (isComplete) {
2920         setPosition(m_searchStartPosition);
2921         if (!needle.isEmpty())
2922             recordJump();
2923     }
2924 
2925     SearchData sd;
2926     sd.needle = needle;
2927     sd.forward = g.lastSearchForward;
2928     sd.highlightMatches = isComplete;
2929     search(sd, isComplete);
2930 }
2931 
resetCount()2932 void FakeVimHandler::Private::resetCount()
2933 {
2934     g.mvcount = 0;
2935     g.opcount = 0;
2936 }
2937 
isInputCount(const Input & input) const2938 bool FakeVimHandler::Private::isInputCount(const Input &input) const
2939 {
2940     return input.isDigit() && (!input.is('0') || g.mvcount > 0);
2941 }
2942 
atEmptyLine(const QTextCursor & tc) const2943 bool FakeVimHandler::Private::atEmptyLine(const QTextCursor &tc) const
2944 {
2945     if (tc.isNull())
2946         return atEmptyLine(m_cursor);
2947     return tc.block().length() == 1;
2948 }
2949 
atBoundary(bool end,bool simple,bool onlyWords,const QTextCursor & tc) const2950 bool FakeVimHandler::Private::atBoundary(bool end, bool simple, bool onlyWords,
2951     const QTextCursor &tc) const
2952 {
2953     if (tc.isNull())
2954         return atBoundary(end, simple, onlyWords, m_cursor);
2955     if (atEmptyLine(tc))
2956         return true;
2957     int pos = tc.position();
2958     QChar c1 = document()->characterAt(pos);
2959     QChar c2 = document()->characterAt(pos + (end ? 1 : -1));
2960     int thisClass = charClass(c1, simple);
2961     return (!onlyWords || thisClass != 0)
2962         && (c2.isNull() || c2 == ParagraphSeparator || thisClass != charClass(c2, simple));
2963 }
2964 
atWordBoundary(bool end,bool simple,const QTextCursor & tc) const2965 bool FakeVimHandler::Private::atWordBoundary(bool end, bool simple, const QTextCursor &tc) const
2966 {
2967     return atBoundary(end, simple, true, tc);
2968 }
2969 
atWordStart(bool simple,const QTextCursor & tc) const2970 bool FakeVimHandler::Private::atWordStart(bool simple, const QTextCursor &tc) const
2971 {
2972     return atWordBoundary(false, simple, tc);
2973 }
2974 
atWordEnd(bool simple,const QTextCursor & tc) const2975 bool FakeVimHandler::Private::atWordEnd(bool simple, const QTextCursor &tc) const
2976 {
2977     return atWordBoundary(true, simple, tc);
2978 }
2979 
isFirstNonBlankOnLine(int pos)2980 bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
2981 {
2982     for (int i = document()->findBlock(pos).position(); i < pos; ++i) {
2983         if (!document()->characterAt(i).isSpace())
2984             return false;
2985     }
2986     return true;
2987 }
2988 
pushUndoState(bool overwrite)2989 void FakeVimHandler::Private::pushUndoState(bool overwrite)
2990 {
2991     if (m_buffer->editBlockLevel != 0 && m_buffer->undoState.isValid())
2992         return; // No need to save undo state for inner edit blocks.
2993 
2994     if (m_buffer->undoState.isValid() && !overwrite)
2995         return;
2996 
2997     UNDO_DEBUG("PUSH UNDO");
2998     int pos = position();
2999     if (!isInsertMode()) {
3000         if (isVisualMode() || g.submode == DeleteSubMode
3001             || (g.submode == ChangeSubMode && g.movetype != MoveLineWise)) {
3002             pos = qMin(pos, anchor());
3003             if (isVisualLineMode())
3004                 pos = firstPositionInLine(lineForPosition(pos));
3005         } else if (g.movetype == MoveLineWise && hasConfig(ConfigStartOfLine)) {
3006             QTextCursor tc = m_cursor;
3007             if (g.submode == ShiftLeftSubMode || g.submode == ShiftRightSubMode
3008                 || g.submode == IndentSubMode) {
3009                 pos = qMin(pos, anchor());
3010             }
3011             tc.setPosition(pos);
3012             moveToFirstNonBlankOnLine(&tc);
3013             pos = qMin(pos, tc.position());
3014         }
3015     }
3016 
3017     m_buffer->redo.clear();
3018     m_buffer->lastChangePosition = CursorPosition(document(), pos);
3019     if (isVisualMode()) {
3020         setMark(QLatin1Char('<'), markLessPosition());
3021         setMark(QLatin1Char('>'), markGreaterPosition());
3022     }
3023     m_buffer->undoState = State(revision(), m_buffer->lastChangePosition, m_buffer->marks,
3024                               m_buffer->lastVisualMode, m_buffer->lastVisualModeInverted);
3025 }
3026 
moveDown(int n)3027 void FakeVimHandler::Private::moveDown(int n)
3028 {
3029     if (n == 0)
3030         return;
3031 
3032     QTextBlock block = m_cursor.block();
3033     const int col = position() - block.position();
3034 
3035     int lines = qAbs(n);
3036     int position = 0;
3037     while (block.isValid()) {
3038         position = block.position() + qMax(0, qMin(block.length() - 2, col));
3039         if (block.isVisible()) {
3040             --lines;
3041             if (lines < 0)
3042                 break;
3043         }
3044         block = n > 0 ? nextLine(block) : previousLine(block);
3045     }
3046 
3047     setPosition(position);
3048     moveToTargetColumn();
3049     updateScrollOffset();
3050 }
3051 
moveDownVisually(int n)3052 void FakeVimHandler::Private::moveDownVisually(int n)
3053 {
3054     const QTextCursor::MoveOperation moveOperation = (n > 0) ? Down : Up;
3055     int count = qAbs(n);
3056     int oldPos = m_cursor.position();
3057 
3058     while (count > 0) {
3059         m_cursor.movePosition(moveOperation, KeepAnchor, 1);
3060         if (oldPos == m_cursor.position())
3061             break;
3062         oldPos = m_cursor.position();
3063         QTextBlock block = m_cursor.block();
3064         if (block.isVisible())
3065             --count;
3066     }
3067 
3068     QTextCursor tc = m_cursor;
3069     tc.movePosition(StartOfLine);
3070     const int minPos = tc.position();
3071     moveToEndOfLineVisually(&tc);
3072     const int maxPos = tc.position();
3073 
3074     if (m_targetColumn == -1) {
3075         setPosition(maxPos);
3076     } else {
3077         setPosition(qMin(maxPos, minPos + m_targetColumnWrapped));
3078         const int targetColumn = m_targetColumnWrapped;
3079         setTargetColumn();
3080         m_targetColumnWrapped = targetColumn;
3081     }
3082 
3083     updateScrollOffset();
3084 }
3085 
movePageDown(int count)3086 void FakeVimHandler::Private::movePageDown(int count)
3087 {
3088     const int scrollOffset = windowScrollOffset();
3089     const int screenLines = linesOnScreen();
3090     const int offset = count > 0 ? scrollOffset - 2 : screenLines - scrollOffset + 2;
3091     const int value = count * screenLines - cursorLineOnScreen() + offset;
3092     moveDown(value);
3093 
3094     if (count > 0)
3095         scrollToLine(cursorLine());
3096     else
3097         scrollToLine(qMax(0, cursorLine() - screenLines + 1));
3098 }
3099 
moveToNextParagraph(int count)3100 bool FakeVimHandler::Private::moveToNextParagraph(int count)
3101 {
3102     const bool forward = count > 0;
3103     int repeat = forward ? count : -count;
3104     int pos = position();
3105     QTextBlock block = this->block();
3106 
3107     if (block.isValid() && block.length() == 1)
3108         ++repeat;
3109 
3110     for (; block.isValid(); block = forward ? block.next() : block.previous()) {
3111         if (block.length() == 1) {
3112             if (--repeat == 0)
3113                 break;
3114             while (block.isValid() && block.length() == 1)
3115                 block = forward ? block.next() : block.previous();
3116         }
3117     }
3118 
3119     if (repeat == 0)
3120         setPosition(block.position());
3121     else if (repeat == 1)
3122         setPosition(forward ? lastPositionInDocument() : 0);
3123     else
3124         return false;
3125 
3126     recordJump(pos);
3127     setTargetColumn();
3128     g.movetype = MoveExclusive;
3129 
3130     return true;
3131 }
3132 
moveToEndOfLine()3133 void FakeVimHandler::Private::moveToEndOfLine()
3134 {
3135     // Additionally select (in visual mode) or apply current command on hidden lines following
3136     // the current line.
3137     bool onlyVisibleLines = isVisualMode() || g.submode != NoSubMode;
3138     const int id = onlyVisibleLines ? lineNumber(block()) : block().blockNumber() + 1;
3139     setPosition(lastPositionInLine(id, onlyVisibleLines));
3140     setTargetColumn();
3141 }
3142 
moveToEndOfLineVisually()3143 void FakeVimHandler::Private::moveToEndOfLineVisually()
3144 {
3145     moveToEndOfLineVisually(&m_cursor);
3146     setTargetColumn();
3147 }
3148 
moveToEndOfLineVisually(QTextCursor * tc)3149 void FakeVimHandler::Private::moveToEndOfLineVisually(QTextCursor *tc)
3150 {
3151     // Moving to end of line ends up on following line if the line is wrapped.
3152     tc->movePosition(StartOfLine);
3153     const int minPos = tc->position();
3154     tc->movePosition(EndOfLine);
3155     int maxPos = tc->position();
3156     tc->movePosition(StartOfLine);
3157     if (minPos != tc->position())
3158         --maxPos;
3159     tc->setPosition(maxPos);
3160 }
3161 
moveBehindEndOfLine()3162 void FakeVimHandler::Private::moveBehindEndOfLine()
3163 {
3164     emit q->fold(1, false);
3165     int pos = qMin(block().position() + block().length() - 1,
3166         lastPositionInDocument() + 1);
3167     setPosition(pos);
3168 }
3169 
moveToStartOfLine()3170 void FakeVimHandler::Private::moveToStartOfLine()
3171 {
3172     setPosition(block().position());
3173     setTargetColumn();
3174 }
3175 
moveToStartOfLineVisually()3176 void FakeVimHandler::Private::moveToStartOfLineVisually()
3177 {
3178     m_cursor.movePosition(StartOfLine, KeepAnchor);
3179     setTargetColumn();
3180 }
3181 
fixSelection()3182 void FakeVimHandler::Private::fixSelection()
3183 {
3184     if (g.rangemode == RangeBlockMode)
3185          return;
3186 
3187     if (g.movetype == MoveInclusive) {
3188         // If position or anchor is after end of non-empty line, include line break in selection.
3189         if (document()->characterAt(position()) == ParagraphSeparator) {
3190             if (!atEmptyLine()) {
3191                 setPosition(position() + 1);
3192                 return;
3193             }
3194         } else if (document()->characterAt(anchor()) == ParagraphSeparator) {
3195             QTextCursor tc = m_cursor;
3196             tc.setPosition(anchor());
3197             if (!atEmptyLine(tc)) {
3198                 setAnchorAndPosition(anchor() + 1, position());
3199                 return;
3200             }
3201         }
3202     }
3203 
3204     if (g.movetype == MoveExclusive && g.subsubmode == NoSubSubMode) {
3205         if (anchor() < position() && atBlockStart()) {
3206             // Exclusive motion ending at the beginning of line
3207             // becomes inclusive and end is moved to end of previous line.
3208             g.movetype = MoveInclusive;
3209             moveToStartOfLine();
3210             moveLeft();
3211 
3212             // Exclusive motion ending at the beginning of line and
3213             // starting at or before first non-blank on a line becomes linewise.
3214             if (anchor() < block().position() && isFirstNonBlankOnLine(anchor()))
3215                 g.movetype = MoveLineWise;
3216         }
3217     }
3218 
3219     if (g.movetype == MoveLineWise)
3220         g.rangemode = (g.submode == ChangeSubMode)
3221             ? RangeLineModeExclusive
3222             : RangeLineMode;
3223 
3224     if (g.movetype == MoveInclusive) {
3225         if (anchor() <= position()) {
3226             if (!atBlockEnd())
3227                 setPosition(position() + 1); // correction
3228 
3229             // Omit first character in selection if it's line break on non-empty line.
3230             int start = anchor();
3231             int end = position();
3232             if (afterEndOfLine(document(), start) && start > 0) {
3233                 start = qMin(start + 1, end);
3234                 if (g.submode == DeleteSubMode && !atDocumentEnd())
3235                     setAnchorAndPosition(start, end + 1);
3236                 else
3237                     setAnchorAndPosition(start, end);
3238             }
3239 
3240             // If more than one line is selected and all are selected completely
3241             // movement becomes linewise.
3242             if (start < block().position() && isFirstNonBlankOnLine(start) && atBlockEnd()) {
3243                 if (g.submode != ChangeSubMode) {
3244                     moveRight();
3245                     if (atEmptyLine())
3246                         moveRight();
3247                 }
3248                 g.movetype = MoveLineWise;
3249             }
3250         } else if (!m_anchorPastEnd) {
3251             setAnchorAndPosition(anchor() + 1, position());
3252         }
3253     }
3254 
3255     if (m_positionPastEnd) {
3256         moveBehindEndOfLine();
3257         moveRight();
3258         setAnchorAndPosition(anchor(), position());
3259     }
3260 
3261     if (m_anchorPastEnd) {
3262         const int pos = position();
3263         setPosition(anchor());
3264         moveBehindEndOfLine();
3265         moveRight();
3266         setAnchorAndPosition(position(), pos);
3267     }
3268 }
3269 
finishSearch()3270 bool FakeVimHandler::Private::finishSearch()
3271 {
3272     if (g.lastSearch.isEmpty()
3273         || (!g.currentMessage.isEmpty() && g.currentMessageLevel == MessageError)) {
3274         return false;
3275     }
3276     if (g.submode != NoSubMode)
3277         setAnchorAndPosition(m_searchStartPosition, position());
3278     return true;
3279 }
3280 
finishMovement(const QString & dotCommandMovement)3281 void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
3282 {
3283     //dump("FINISH MOVEMENT");
3284     if (g.submode == FilterSubMode) {
3285         int beginLine = lineForPosition(anchor());
3286         int endLine = lineForPosition(position());
3287         setPosition(qMin(anchor(), position()));
3288         enterExMode(QString::fromLatin1(".,+%1!").arg(qAbs(endLine - beginLine)));
3289         return;
3290     }
3291 
3292     if (g.submode == ChangeSubMode
3293         || g.submode == DeleteSubMode
3294         || g.submode == YankSubMode
3295         || g.submode == InvertCaseSubMode
3296         || g.submode == DownCaseSubMode
3297         || g.submode == UpCaseSubMode) {
3298         fixSelection();
3299 
3300         if (g.submode != InvertCaseSubMode
3301             && g.submode != DownCaseSubMode
3302             && g.submode != UpCaseSubMode) {
3303             yankText(currentRange(), m_register);
3304             if (g.movetype == MoveLineWise)
3305                 setRegister(m_register, registerContents(m_register), RangeLineMode);
3306         }
3307 
3308         m_positionPastEnd = m_anchorPastEnd = false;
3309     }
3310 
3311     QString dotCommand;
3312     if (g.submode == ChangeSubMode) {
3313         pushUndoState(false);
3314         beginEditBlock();
3315         removeText(currentRange());
3316         dotCommand = _("c");
3317         if (g.movetype == MoveLineWise)
3318             insertAutomaticIndentation(true);
3319         endEditBlock();
3320         setTargetColumn();
3321     } else if (g.submode == DeleteSubMode) {
3322         pushUndoState(false);
3323         beginEditBlock();
3324         const int pos = position();
3325         // Always delete something (e.g. 'dw' on an empty line deletes the line).
3326         if (pos == anchor() && g.movetype == MoveInclusive)
3327             removeText(Range(pos, pos + 1));
3328         else
3329             removeText(currentRange());
3330         dotCommand = _("d");
3331         if (g.movetype == MoveLineWise)
3332             handleStartOfLine();
3333         if (atEndOfLine())
3334             moveLeft();
3335         else
3336             setTargetColumn();
3337         endEditBlock();
3338     } else if (g.submode == YankSubMode) {
3339         bool isVisualModeYank = isVisualMode();
3340         leaveVisualMode();
3341         const QTextCursor tc = m_cursor;
3342         if (g.rangemode == RangeBlockMode) {
3343             const int pos1 = tc.block().position();
3344             const int pos2 = document()->findBlock(tc.anchor()).position();
3345             const int col = qMin(tc.position() - pos1, tc.anchor() - pos2);
3346             setPosition(qMin(pos1, pos2) + col);
3347         } else {
3348             setPosition(qMin(position(), anchor()));
3349             if (g.rangemode == RangeLineMode) {
3350                 if (isVisualModeYank)
3351                     moveToStartOfLine();
3352             }
3353         }
3354         setTargetColumn();
3355     } else if (g.submode == InvertCaseSubMode
3356         || g.submode == UpCaseSubMode
3357         || g.submode == DownCaseSubMode) {
3358         beginEditBlock();
3359         if (g.submode == InvertCaseSubMode) {
3360             invertCase(currentRange());
3361             dotCommand = QString::fromLatin1("g~");
3362         } else if (g.submode == DownCaseSubMode) {
3363             downCase(currentRange());
3364             dotCommand = QString::fromLatin1("gu");
3365         } else if (g.submode == UpCaseSubMode) {
3366             upCase(currentRange());
3367             dotCommand = QString::fromLatin1("gU");
3368         }
3369         if (g.movetype == MoveLineWise)
3370             handleStartOfLine();
3371         endEditBlock();
3372     } else if (g.submode == IndentSubMode
3373         || g.submode == ShiftRightSubMode
3374         || g.submode == ShiftLeftSubMode) {
3375         recordJump();
3376         pushUndoState(false);
3377         if (g.submode == IndentSubMode) {
3378             indentSelectedText();
3379             dotCommand = _("=");
3380         } else if (g.submode == ShiftRightSubMode) {
3381             shiftRegionRight(1);
3382             dotCommand = _(">");
3383         } else if (g.submode == ShiftLeftSubMode) {
3384             shiftRegionLeft(1);
3385             dotCommand = _("<");
3386         }
3387     }
3388 
3389     if (!dotCommand.isEmpty() && !dotCommandMovement.isEmpty())
3390         setDotCommand(dotCommand + dotCommandMovement);
3391 
3392     // Change command continues in insert mode.
3393     if (g.submode == ChangeSubMode) {
3394         clearCommandMode();
3395         enterInsertMode();
3396     } else {
3397         resetCommandMode();
3398     }
3399 }
3400 
resetCommandMode()3401 void FakeVimHandler::Private::resetCommandMode()
3402 {
3403     if (g.returnToMode == CommandMode) {
3404         enterCommandMode();
3405     } else {
3406         clearCommandMode();
3407         const QString lastInsertion = m_buffer->lastInsertion;
3408         if (g.returnToMode == InsertMode)
3409             enterInsertMode();
3410         else
3411             enterReplaceMode();
3412         moveToTargetColumn();
3413         invalidateInsertState();
3414         m_buffer->lastInsertion = lastInsertion;
3415     }
3416     if (isNoVisualMode())
3417         setAnchor();
3418 }
3419 
clearCommandMode()3420 void FakeVimHandler::Private::clearCommandMode()
3421 {
3422     g.submode = NoSubMode;
3423     g.subsubmode = NoSubSubMode;
3424     g.movetype = MoveInclusive;
3425     g.gflag = false;
3426     m_register = '"';
3427     g.rangemode = RangeCharMode;
3428     g.currentCommand.clear();
3429     resetCount();
3430 }
3431 
updateSelection()3432 void FakeVimHandler::Private::updateSelection()
3433 {
3434     QList<QTextEdit::ExtraSelection> selections = m_extraSelections;
3435     if (hasConfig(ConfigShowMarks)) {
3436         for (MarksIterator it(m_buffer->marks); it.hasNext(); ) {
3437             it.next();
3438             QTextEdit::ExtraSelection sel;
3439             sel.cursor = m_cursor;
3440             setCursorPosition(&sel.cursor, it.value().position(document()));
3441             sel.cursor.setPosition(sel.cursor.position(), MoveAnchor);
3442             sel.cursor.movePosition(Right, KeepAnchor);
3443             sel.format = m_cursor.blockCharFormat();
3444             sel.format.setForeground(Qt::blue);
3445             sel.format.setBackground(Qt::green);
3446             selections.append(sel);
3447         }
3448     }
3449     //qDebug() << "SELECTION: " << selections;
3450     emit q->selectionChanged(selections);
3451 }
3452 
updateHighlights()3453 void FakeVimHandler::Private::updateHighlights()
3454 {
3455     if (hasConfig(ConfigUseCoreSearch) || !hasConfig(ConfigHlSearch) || g.highlightsCleared) {
3456         if (m_highlighted.isEmpty())
3457             return;
3458         m_highlighted.clear();
3459     } else if (m_highlighted != g.lastNeedle) {
3460         m_highlighted = g.lastNeedle;
3461     } else {
3462         return;
3463     }
3464 
3465     emit q->highlightMatches(m_highlighted);
3466 }
3467 
updateMiniBuffer()3468 void FakeVimHandler::Private::updateMiniBuffer()
3469 {
3470     if (!m_textedit && !m_plaintextedit)
3471         return;
3472 
3473     QString msg;
3474     int cursorPos = -1;
3475     int anchorPos = -1;
3476     MessageLevel messageLevel = MessageMode;
3477 
3478     if (!g.mapStates.isEmpty() && g.mapStates.last().silent && g.currentMessageLevel < MessageInfo)
3479         g.currentMessage.clear();
3480 
3481     if (g.passing) {
3482         msg = _("PASSING");
3483     } else if (g.subsubmode == SearchSubSubMode) {
3484         msg = g.searchBuffer.display();
3485         if (g.mapStates.isEmpty()) {
3486             cursorPos = g.searchBuffer.cursorPos() + 1;
3487             anchorPos = g.searchBuffer.anchorPos() + 1;
3488         }
3489     } else if (g.mode == ExMode) {
3490         msg = g.commandBuffer.display();
3491         if (g.mapStates.isEmpty()) {
3492             cursorPos = g.commandBuffer.cursorPos() + 1;
3493             anchorPos = g.commandBuffer.anchorPos() + 1;
3494         }
3495     } else if (!g.currentMessage.isEmpty()) {
3496         msg = g.currentMessage;
3497         g.currentMessage.clear();
3498         messageLevel = g.currentMessageLevel;
3499     } else if (!g.mapStates.isEmpty() && !g.mapStates.last().silent) {
3500         // Do not reset previous message when after running a mapped command.
3501         return;
3502     } else if (g.mode == CommandMode && !g.currentCommand.isEmpty() && hasConfig(ConfigShowCmd)) {
3503         msg = g.currentCommand;
3504         messageLevel = MessageShowCmd;
3505     } else if (g.mode == CommandMode && isVisualMode()) {
3506         if (isVisualCharMode())
3507             msg = _("-- VISUAL --");
3508         else if (isVisualLineMode())
3509             msg = _("-- VISUAL LINE --");
3510         else if (isVisualBlockMode())
3511             msg = _("VISUAL BLOCK");
3512     } else if (g.mode == InsertMode) {
3513         msg = _("-- INSERT --");
3514     } else if (g.mode == ReplaceMode) {
3515         msg = _("-- REPLACE --");
3516     } else {
3517         QTC_CHECK(g.mode == CommandMode && g.subsubmode != SearchSubSubMode);
3518         if (g.returnToMode == CommandMode)
3519             msg = _("-- COMMAND --");
3520         else if (g.returnToMode == InsertMode)
3521             msg = _("-- (insert) --");
3522         else
3523             msg = _("-- (replace) --");
3524     }
3525 
3526     if (!g.recording.isNull() && msg.startsWith(_("--")))
3527         msg.append(_("recording"));
3528 
3529     emit q->commandBufferChanged(msg, cursorPos, anchorPos, messageLevel, q);
3530 
3531     int linesInDoc = linesInDocument();
3532     int l = cursorLine();
3533     QString status;
3534     const QString pos = QString::fromLatin1("%1,%2")
3535         .arg(l + 1).arg(physicalCursorColumn() + 1);
3536     // FIXME: physical "-" logical
3537     if (linesInDoc != 0)
3538         status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
3539     else
3540         status = FakeVimHandler::tr("%1All").arg(pos, -10);
3541     emit q->statusDataChanged(status);
3542 }
3543 
showMessage(MessageLevel level,const QString & msg)3544 void FakeVimHandler::Private::showMessage(MessageLevel level, const QString &msg)
3545 {
3546     //qDebug() << "MSG: " << msg;
3547     g.currentMessage = msg;
3548     g.currentMessageLevel = level;
3549 }
3550 
notImplementedYet()3551 void FakeVimHandler::Private::notImplementedYet()
3552 {
3553     qDebug() << "Not implemented in FakeVim";
3554     showMessage(MessageError, FakeVimHandler::tr("Not implemented in FakeVim."));
3555 }
3556 
passShortcuts(bool enable)3557 void FakeVimHandler::Private::passShortcuts(bool enable)
3558 {
3559     g.passing = enable;
3560     updateMiniBuffer();
3561     if (enable)
3562         QCoreApplication::instance()->installEventFilter(q);
3563     else
3564         QCoreApplication::instance()->removeEventFilter(q);
3565 }
3566 
handleCommandSubSubMode(const Input & input)3567 bool FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
3568 {
3569     //const int key = input.key;
3570     bool handled = true;
3571     if (g.subsubmode == FtSubSubMode) {
3572         g.semicolonType = g.subsubdata;
3573         g.semicolonKey = input.text();
3574         bool valid = handleFfTt(g.semicolonKey);
3575         g.subsubmode = NoSubSubMode;
3576         if (!valid) {
3577             g.submode = NoSubMode;
3578             resetCommandMode();
3579             handled = false;
3580         } else {
3581             finishMovement(QString::fromLatin1("%1%2%3")
3582                            .arg(count())
3583                            .arg(g.semicolonType.text())
3584                            .arg(g.semicolonKey));
3585         }
3586     } else if (g.subsubmode == TextObjectSubSubMode) {
3587         bool ok = true;
3588         if (input.is('w'))
3589             selectWordTextObject(g.subsubdata.is('i'));
3590         else if (input.is('W'))
3591             selectWORDTextObject(g.subsubdata.is('i'));
3592         else if (input.is('s'))
3593             selectSentenceTextObject(g.subsubdata.is('i'));
3594         else if (input.is('p'))
3595             selectParagraphTextObject(g.subsubdata.is('i'));
3596         else if (input.is('[') || input.is(']'))
3597             ok = selectBlockTextObject(g.subsubdata.is('i'), '[', ']');
3598         else if (input.is('(') || input.is(')') || input.is('b'))
3599             ok = selectBlockTextObject(g.subsubdata.is('i'), '(', ')');
3600         else if (input.is('<') || input.is('>'))
3601             ok = selectBlockTextObject(g.subsubdata.is('i'), '<', '>');
3602         else if (input.is('{') || input.is('}') || input.is('B'))
3603             ok = selectBlockTextObject(g.subsubdata.is('i'), '{', '}');
3604         else if (input.is('"') || input.is('\'') || input.is('`'))
3605             ok = selectQuotedStringTextObject(g.subsubdata.is('i'), input.asChar());
3606         else
3607             ok = false;
3608         g.subsubmode = NoSubSubMode;
3609         if (ok) {
3610             finishMovement(QString::fromLatin1("%1%2%3")
3611                            .arg(count())
3612                            .arg(g.subsubdata.text())
3613                            .arg(input.text()));
3614         } else {
3615             resetCommandMode();
3616             handled = false;
3617         }
3618     } else if (g.subsubmode == MarkSubSubMode) {
3619         setMark(input.asChar(), CursorPosition(m_cursor));
3620         g.subsubmode = NoSubSubMode;
3621     } else if (g.subsubmode == BackTickSubSubMode
3622             || g.subsubmode == TickSubSubMode) {
3623         if (jumpToMark(input.asChar(), g.subsubmode == BackTickSubSubMode)) {
3624             finishMovement();
3625         } else {
3626             resetCommandMode();
3627             handled = false;
3628         }
3629         g.subsubmode = NoSubSubMode;
3630     } else if (g.subsubmode == ZSubSubMode) {
3631         handled = false;
3632         if (input.is('j') || input.is('k')) {
3633             int pos = position();
3634             emit q->foldGoTo(input.is('j') ? count() : -count(), false);
3635             if (pos != position()) {
3636                 handled = true;
3637                 finishMovement(QString::fromLatin1("%1z%2")
3638                                .arg(count())
3639                                .arg(input.text()));
3640             }
3641         }
3642     } else if (g.subsubmode == OpenSquareSubSubMode || g.subsubmode == CloseSquareSubSubMode) {
3643         int pos = position();
3644         if (input.is('{') && g.subsubmode == OpenSquareSubSubMode)
3645             searchBalanced(false, QLatin1Char('{'), QLatin1Char('}'));
3646         else if (input.is('}') && g.subsubmode == CloseSquareSubSubMode)
3647             searchBalanced(true, QLatin1Char('}'), QLatin1Char('{'));
3648         else if (input.is('(') && g.subsubmode == OpenSquareSubSubMode)
3649             searchBalanced(false, QLatin1Char('('), QLatin1Char(')'));
3650         else if (input.is(')') && g.subsubmode == CloseSquareSubSubMode)
3651             searchBalanced(true, QLatin1Char(')'), QLatin1Char('('));
3652         else if (input.is('[') && g.subsubmode == OpenSquareSubSubMode)
3653             bracketSearchBackward(&m_cursor, _("^\\{"), count());
3654         else if (input.is('[') && g.subsubmode == CloseSquareSubSubMode)
3655             bracketSearchForward(&m_cursor, _("^\\}"), count(), false);
3656         else if (input.is(']') && g.subsubmode == OpenSquareSubSubMode)
3657             bracketSearchBackward(&m_cursor, _("^\\}"), count());
3658         else if (input.is(']') && g.subsubmode == CloseSquareSubSubMode)
3659             bracketSearchForward(&m_cursor, _("^\\{"), count(), g.submode != NoSubMode);
3660         else if (input.is('z'))
3661             emit q->foldGoTo(g.subsubmode == OpenSquareSubSubMode ? -count() : count(), true);
3662         handled = pos != position();
3663         if (handled) {
3664             if (lineForPosition(pos) != lineForPosition(position()))
3665                 recordJump(pos);
3666             finishMovement(QString::fromLatin1("%1%2%3")
3667                            .arg(count())
3668                            .arg(g.subsubmode == OpenSquareSubSubMode ? '[' : ']')
3669                            .arg(input.text()));
3670         }
3671     } else {
3672         handled = false;
3673     }
3674     return handled;
3675 }
3676 
handleCount(const Input & input)3677 bool FakeVimHandler::Private::handleCount(const Input &input)
3678 {
3679     if (!isInputCount(input))
3680         return false;
3681     g.mvcount = g.mvcount * 10 + input.text().toInt();
3682     return true;
3683 }
3684 
handleMovement(const Input & input)3685 bool FakeVimHandler::Private::handleMovement(const Input &input)
3686 {
3687     bool handled = true;
3688     QString movement;
3689     int count = this->count();
3690 
3691     if (handleCount(input)) {
3692         return true;
3693     } else if (input.is('0')) {
3694         g.movetype = MoveExclusive;
3695         if (g.gflag)
3696             moveToStartOfLineVisually();
3697         else
3698             moveToStartOfLine();
3699         count = 1;
3700     } else if (input.is('a') || input.is('i')) {
3701         g.subsubmode = TextObjectSubSubMode;
3702         g.subsubdata = input;
3703     } else if (input.is('^') || input.is('_')) {
3704         if (g.gflag)
3705             moveToFirstNonBlankOnLineVisually();
3706         else
3707             moveToFirstNonBlankOnLine();
3708         g.movetype = MoveExclusive;
3709     } else if (0 && input.is(',')) {
3710         // FIXME: fakevim uses ',' by itself, so it is incompatible
3711         g.subsubmode = FtSubSubMode;
3712         // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
3713         //g.subsubdata = g.semicolonType ^ 32;
3714         handleFfTt(g.semicolonKey, true);
3715         g.subsubmode = NoSubSubMode;
3716     } else if (input.is(';')) {
3717         g.subsubmode = FtSubSubMode;
3718         g.subsubdata = g.semicolonType;
3719         handleFfTt(g.semicolonKey, true);
3720         g.subsubmode = NoSubSubMode;
3721     } else if (input.is('/') || input.is('?')) {
3722         g.lastSearchForward = input.is('/');
3723         if (hasConfig(ConfigUseCoreSearch)) {
3724             // re-use the core dialog.
3725             g.findPending = true;
3726             m_findStartPosition = position();
3727             g.movetype = MoveExclusive;
3728             setAnchor(); // clear selection: otherwise, search is restricted to selection
3729             emit q->findRequested(!g.lastSearchForward);
3730         } else {
3731             // FIXME: make core find dialog sufficiently flexible to
3732             // produce the "default vi" behaviour too. For now, roll our own.
3733             g.currentMessage.clear();
3734             g.movetype = MoveExclusive;
3735             g.subsubmode = SearchSubSubMode;
3736             g.searchBuffer.setPrompt(g.lastSearchForward ? QLatin1Char('/') : QLatin1Char('?'));
3737             m_searchStartPosition = position();
3738             m_searchFromScreenLine = firstVisibleLine();
3739             m_searchCursor = QTextCursor();
3740             g.searchBuffer.clear();
3741         }
3742     } else if (input.is('`')) {
3743         g.subsubmode = BackTickSubSubMode;
3744     } else if (input.is('#') || input.is('*')) {
3745         // FIXME: That's not proper vim behaviour
3746         QString needle;
3747         QTextCursor tc = m_cursor;
3748         tc.select(QTextCursor::WordUnderCursor);
3749         needle = QRegExp::escape(tc.selection().toPlainText());
3750         if (!g.gflag)
3751             needle = _("\\<") + needle + _("\\>");
3752         setAnchorAndPosition(tc.position(), tc.anchor());
3753         g.searchBuffer.historyPush(needle);
3754         g.lastSearch = needle;
3755         g.lastSearchForward = input.is('*');
3756         handled = searchNext();
3757     } else if (input.is('\'')) {
3758         g.subsubmode = TickSubSubMode;
3759         if (g.submode != NoSubMode)
3760             g.movetype = MoveLineWise;
3761     } else if (input.is('|')) {
3762         moveToStartOfLine();
3763         moveRight(qMin(count, rightDist()) - 1);
3764         setTargetColumn();
3765     } else if (input.is('}')) {
3766         handled = moveToNextParagraph(count);
3767     } else if (input.is('{')) {
3768         handled = moveToPreviousParagraph(count);
3769     } else if (input.isReturn()) {
3770         moveToStartOfLine();
3771         moveDown();
3772         moveToFirstNonBlankOnLine();
3773         g.movetype = MoveLineWise;
3774     } else if (input.is('-')) {
3775         moveToStartOfLine();
3776         moveUp(count);
3777         moveToFirstNonBlankOnLine();
3778         g.movetype = MoveLineWise;
3779     } else if (input.is('+')) {
3780         moveToStartOfLine();
3781         moveDown(count);
3782         moveToFirstNonBlankOnLine();
3783         g.movetype = MoveLineWise;
3784     } else if (input.isKey(Key_Home)) {
3785         moveToStartOfLine();
3786         setTargetColumn();
3787         movement = _("<HOME>");
3788     } else if (input.is('$') || input.isKey(Key_End)) {
3789         if (g.gflag) {
3790             if (count > 1)
3791                 moveDownVisually(count - 1);
3792             moveToEndOfLineVisually();
3793         } else {
3794             if (count > 1)
3795                 moveDown(count - 1);
3796             moveToEndOfLine();
3797         }
3798         g.movetype = atEmptyLine() ? MoveExclusive : MoveInclusive;
3799         setTargetColumn();
3800         if (g.submode == NoSubMode)
3801             m_targetColumn = -1;
3802         if (isVisualMode())
3803             m_visualTargetColumn = -1;
3804         movement = _("$");
3805     } else if (input.is('%')) {
3806         recordJump();
3807         if (g.mvcount == 0) {
3808             moveToMatchingParanthesis();
3809             g.movetype = MoveInclusive;
3810         } else {
3811             // set cursor position in percentage - formula taken from Vim help
3812             setPosition(firstPositionInLine((count * linesInDocument() + 99) / 100));
3813             moveToTargetColumn();
3814             handleStartOfLine();
3815             g.movetype = MoveLineWise;
3816         }
3817     } else if (input.is('b') || input.isShift(Key_Left)) {
3818         g.movetype = MoveExclusive;
3819         moveToNextWordStart(count, false, false);
3820         setTargetColumn();
3821         movement = _("b");
3822     } else if (input.is('B')) {
3823         g.movetype = MoveExclusive;
3824         moveToNextWordStart(count, true, false);
3825         setTargetColumn();
3826     } else if (input.is('e') && g.gflag) {
3827         g.movetype = MoveInclusive;
3828         moveToNextWordEnd(count, false, false);
3829         setTargetColumn();
3830     } else if (input.is('e') || input.isShift(Key_Right)) {
3831         g.movetype = MoveInclusive;
3832         moveToNextWordEnd(count, false, true, false);
3833         setTargetColumn();
3834         movement = _("e");
3835     } else if (input.is('E') && g.gflag) {
3836         g.movetype = MoveInclusive;
3837         moveToNextWordEnd(count, true, false);
3838         setTargetColumn();
3839     } else if (input.is('E')) {
3840         g.movetype = MoveInclusive;
3841         moveToNextWordEnd(count, true, true, false);
3842         setTargetColumn();
3843     } else if (input.isControl('e')) {
3844         // FIXME: this should use the "scroll" option, and "count"
3845         if (cursorLineOnScreen() == 0)
3846             moveDown(1);
3847         scrollDown(1);
3848         movement = _("<C-E>");
3849     } else if (input.is('f')) {
3850         g.subsubmode = FtSubSubMode;
3851         g.movetype = MoveInclusive;
3852         g.subsubdata = input;
3853     } else if (input.is('F')) {
3854         g.subsubmode = FtSubSubMode;
3855         g.movetype = MoveExclusive;
3856         g.subsubdata = input;
3857     } else if (!g.gflag && input.is('g')) {
3858         g.gflag = true;
3859         return true;
3860     } else if (input.is('g') || input.is('G')) {
3861         QString dotCommand = QString::fromLatin1("%1G").arg(count);
3862         recordJump();
3863         if (input.is('G') && g.mvcount == 0)
3864             dotCommand = QString(QLatin1Char('G'));
3865         int n = (input.is('g')) ? 1 : linesInDocument();
3866         n = g.mvcount == 0 ? n : count;
3867         if (g.submode == NoSubMode || g.submode == ZSubMode
3868                 || g.submode == CapitalZSubMode || g.submode == RegisterSubMode) {
3869             setPosition(firstPositionInLine(n, false));
3870             handleStartOfLine();
3871         } else {
3872             g.movetype = MoveLineWise;
3873             g.rangemode = RangeLineMode;
3874             setAnchor();
3875             setPosition(firstPositionInLine(n, false));
3876         }
3877         setTargetColumn();
3878         updateScrollOffset();
3879     } else if (input.is('h') || input.isKey(Key_Left) || input.isBackspace()) {
3880         g.movetype = MoveExclusive;
3881         int n = qMin(count, leftDist());
3882         if (m_fakeEnd && block().length() > 1)
3883             ++n;
3884         moveLeft(n);
3885         setTargetColumn();
3886         movement = _("h");
3887     } else if (input.is('H')) {
3888         const CursorPosition pos(lineToBlockNumber(lineOnTop(count)), 0);
3889         setCursorPosition(&m_cursor, pos);
3890         handleStartOfLine();
3891     } else if (input.is('j') || input.isKey(Key_Down)
3892             || input.isControl('j') || input.isControl('n')) {
3893         if (g.gflag) {
3894             g.movetype = MoveExclusive;
3895             moveDownVisually(count);
3896             movement = _("gj");
3897         } else {
3898             g.movetype = MoveLineWise;
3899             moveDown(count);
3900             movement = _("j");
3901         }
3902     } else if (input.is('k') || input.isKey(Key_Up) || input.isControl('p')) {
3903         if (g.gflag) {
3904             g.movetype = MoveExclusive;
3905             moveUpVisually(count);
3906             movement = _("gk");
3907         } else {
3908             g.movetype = MoveLineWise;
3909             moveUp(count);
3910             movement = _("k");
3911         }
3912     } else if (input.is('l') || input.isKey(Key_Right) || input.is(' ')) {
3913         g.movetype = MoveExclusive;
3914         bool pastEnd = count >= rightDist() - 1;
3915         moveRight(qMax(0, qMin(count, rightDist() - (g.submode == NoSubMode))));
3916         setTargetColumn();
3917         if (pastEnd && isVisualMode())
3918             m_visualTargetColumn = -1;
3919     } else if (input.is('L')) {
3920         const CursorPosition pos(lineToBlockNumber(lineOnBottom(count)), 0);
3921         setCursorPosition(&m_cursor, pos);
3922         handleStartOfLine();
3923     } else if (g.gflag && input.is('m')) {
3924         const QPoint pos(EDITOR(viewport()->width()) / 2, EDITOR(cursorRect(m_cursor)).y());
3925         QTextCursor tc = EDITOR(cursorForPosition(pos));
3926         if (!tc.isNull()) {
3927             m_cursor = tc;
3928             setTargetColumn();
3929         }
3930     } else if (input.is('M')) {
3931         m_cursor = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
3932         handleStartOfLine();
3933     } else if (input.is('n') || input.is('N')) {
3934         if (hasConfig(ConfigUseCoreSearch)) {
3935             bool forward = (input.is('n')) ? g.lastSearchForward : !g.lastSearchForward;
3936             int pos = position();
3937             emit q->findNextRequested(!forward);
3938             if (forward && pos == m_cursor.selectionStart()) {
3939                 // if cursor is already positioned at the start of a find result, this is returned
3940                 emit q->findNextRequested(false);
3941             }
3942             setPosition(m_cursor.selectionStart());
3943         } else {
3944             handled = searchNext(input.is('n'));
3945         }
3946     } else if (input.is('t')) {
3947         g.movetype = MoveInclusive;
3948         g.subsubmode = FtSubSubMode;
3949         g.subsubdata = input;
3950     } else if (input.is('T')) {
3951         g.movetype = MoveExclusive;
3952         g.subsubmode = FtSubSubMode;
3953         g.subsubdata = input;
3954     } else if (input.is('w') || input.is('W')) { // tested
3955         // Special case: "cw" and "cW" work the same as "ce" and "cE" if the
3956         // cursor is on a non-blank - except if the cursor is on the last
3957         // character of a word: only the current word will be changed
3958         bool simple = input.is('W');
3959         if (g.submode == ChangeSubMode && !document()->characterAt(position()).isSpace()) {
3960             moveToWordEnd(count, simple, true);
3961             g.movetype = MoveInclusive;
3962         } else {
3963             moveToNextWordStart(count, simple, true);
3964             // Command 'dw' deletes to the next word on the same line or to end of line.
3965             if (g.submode == DeleteSubMode && count == 1) {
3966                 const QTextBlock currentBlock = document()->findBlock(anchor());
3967                 setPosition(qMin(position(), currentBlock.position() + currentBlock.length()));
3968             }
3969             g.movetype = MoveExclusive;
3970         }
3971         setTargetColumn();
3972     } else if (input.is('z')) {
3973         g.movetype =  MoveLineWise;
3974         g.subsubmode = ZSubSubMode;
3975     } else if (input.is('[')) {
3976         g.subsubmode = OpenSquareSubSubMode;
3977     } else if (input.is(']')) {
3978         g.subsubmode = CloseSquareSubSubMode;
3979     } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
3980         movePageDown(count);
3981         handleStartOfLine();
3982         movement = _("f");
3983     } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
3984         movePageUp(count);
3985         handleStartOfLine();
3986         movement = _("b");
3987     } else {
3988         handled = false;
3989     }
3990 
3991     if (handled && g.subsubmode == NoSubSubMode) {
3992         if (g.submode == NoSubMode) {
3993             resetCommandMode();
3994         } else {
3995             // finish movement for sub modes
3996             const QString dotMovement =
3997                 (count > 1 ? QString::number(count) : QString())
3998                 + _(g.gflag ? "g" : "")
3999                 + (movement.isNull() ? QString(input.asChar()) : movement);
4000             finishMovement(dotMovement);
4001             setTargetColumn();
4002         }
4003     }
4004 
4005     return handled;
4006 }
4007 
handleCommandMode(const Input & input)4008 EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
4009 {
4010     bool handled = false;
4011 
4012     bool clearGflag = g.gflag;
4013     bool clearRegister = g.submode != RegisterSubMode;
4014     bool clearCount = g.submode != RegisterSubMode && !isInputCount(input);
4015 
4016     // Process input for a sub-mode.
4017     if (input.isEscape()) {
4018         handled = handleEscape();
4019     } else if (g.subsubmode != NoSubSubMode) {
4020         handled = handleCommandSubSubMode(input);
4021     } else if (g.submode == NoSubMode) {
4022         handled = handleNoSubMode(input);
4023     } else if (g.submode == ChangeSubMode || g.submode == DeleteSubMode) {
4024         handled = handleChangeDeleteSubModes(input);
4025     } else if (g.submode == ReplaceSubMode) {
4026         handled = handleReplaceSubMode(input);
4027     } else if (g.submode == FilterSubMode) {
4028         handled = handleFilterSubMode(input);
4029     } else if (g.submode == RegisterSubMode) {
4030         handled = handleRegisterSubMode(input);
4031     } else if (g.submode == WindowSubMode) {
4032         handled = handleWindowSubMode(input);
4033     } else if (g.submode == YankSubMode) {
4034         handled = handleYankSubMode(input);
4035     } else if (g.submode == ZSubMode) {
4036         handled = handleZSubMode(input);
4037     } else if (g.submode == CapitalZSubMode) {
4038         handled = handleCapitalZSubMode(input);
4039     } else if (g.submode == MacroRecordSubMode) {
4040         handled = handleMacroRecordSubMode(input);
4041     } else if (g.submode == MacroExecuteSubMode) {
4042         handled = handleMacroExecuteSubMode(input);
4043     } else if (g.submode == ShiftLeftSubMode
4044         || g.submode == ShiftRightSubMode
4045         || g.submode == IndentSubMode) {
4046         handled = handleShiftSubMode(input);
4047     } else if (g.submode == InvertCaseSubMode
4048         || g.submode == DownCaseSubMode
4049         || g.submode == UpCaseSubMode) {
4050         handled = handleChangeCaseSubMode(input);
4051     }
4052 
4053     if (!handled && isOperatorPending())
4054        handled = handleMovement(input);
4055 
4056     // Clear state and display incomplete command if necessary.
4057     if (handled) {
4058         bool noMode =
4059             (g.mode == CommandMode && g.submode == NoSubMode && g.subsubmode == NoSubSubMode);
4060         clearCount = clearCount && noMode && !g.gflag;
4061         if (clearCount && clearRegister) {
4062             resetCommandMode();
4063         } else {
4064             // Use gflag only for next input.
4065             if (clearGflag)
4066                 g.gflag = false;
4067             // Clear [count] and [register] if its no longer needed.
4068             if (clearCount)
4069                 resetCount();
4070             // Show or clear current command on minibuffer (showcmd).
4071             if (input.isEscape() || g.mode != CommandMode || clearCount)
4072                 g.currentCommand.clear();
4073             else
4074                 g.currentCommand.append(input.toString());
4075         }
4076     } else {
4077         resetCommandMode();
4078         //qDebug() << "IGNORED IN COMMAND MODE: " << key << text
4079         //    << " VISUAL: " << g.visualMode;
4080 
4081         // if a key which produces text was pressed, don't mark it as unhandled
4082         // - otherwise the text would be inserted while being in command mode
4083         if (input.text().isEmpty())
4084             handled = false;
4085     }
4086 
4087     updateMiniBuffer();
4088 
4089     m_positionPastEnd = (m_visualTargetColumn == -1) && isVisualMode() && !atEmptyLine();
4090 
4091     return handled ? EventHandled : EventCancelled;
4092 }
4093 
handleEscape()4094 bool FakeVimHandler::Private::handleEscape()
4095 {
4096     if (isVisualMode())
4097         leaveVisualMode();
4098     resetCommandMode();
4099     return true;
4100 }
4101 
handleNoSubMode(const Input & input)4102 bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
4103 {
4104     bool handled = true;
4105 
4106     if (input.is('&')) {
4107         handleExCommand(g.gflag ? _("%s//~/&") : _("s"));
4108     } else if (input.is(':')) {
4109         enterExMode();
4110     } else if (input.is('!') && isNoVisualMode()) {
4111         g.submode = FilterSubMode;
4112     } else if (input.is('!') && isVisualMode()) {
4113         enterExMode(QString::fromLatin1("!"));
4114     } else if (input.is('"')) {
4115         g.submode = RegisterSubMode;
4116     } else if (input.is(',')) {
4117         passShortcuts(true);
4118     } else if (input.is('.')) {
4119         //qDebug() << "REPEATING" << quoteUnprintable(g.dotCommand) << count()
4120         //    << input;
4121         QString savedCommand = g.dotCommand;
4122         g.dotCommand.clear();
4123         beginLargeEditBlock();
4124         replay(savedCommand);
4125         endEditBlock();
4126         resetCommandMode();
4127         g.dotCommand = savedCommand;
4128     } else if (input.is('<') || input.is('>') || input.is('=')) {
4129         if (isNoVisualMode()) {
4130             if (input.is('<'))
4131                 g.submode = ShiftLeftSubMode;
4132             else if (input.is('>'))
4133                 g.submode = ShiftRightSubMode;
4134             else
4135                 g.submode = IndentSubMode;
4136             setAnchor();
4137         } else {
4138             leaveVisualMode();
4139             const int lines = qAbs(lineForPosition(position()) - lineForPosition(anchor())) + 1;
4140             const int repeat = count();
4141             if (input.is('<'))
4142                 shiftRegionLeft(repeat);
4143             else if (input.is('>'))
4144                 shiftRegionRight(repeat);
4145             else
4146                 indentSelectedText();
4147             const QString selectDotCommand =
4148                     (lines > 1) ? QString::fromLatin1("V%1j").arg(lines - 1): QString();
4149             setDotCommand(selectDotCommand + QString::fromLatin1("%1%2%2").arg(repeat).arg(input.raw()));
4150         }
4151     } else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) {
4152         if (isVisualMode()) {
4153             enterVisualInsertMode(QLatin1Char('A'));
4154         } else {
4155             setDotCommand(_("%1a"), count());
4156             moveRight(qMin(rightDist(), 1));
4157             breakEditBlock();
4158             enterInsertMode();
4159         }
4160     } else if (input.is('A')) {
4161         breakEditBlock();
4162         moveBehindEndOfLine();
4163         setAnchor();
4164         enterInsertMode();
4165         setTargetColumn();
4166         setDotCommand(_("%1A"), count());
4167     } else if (input.isControl('a')) {
4168         if (changeNumberTextObject(count()))
4169             setDotCommand(_("%1<c-a>"), count());
4170     } else if ((input.is('c') || input.is('d')) && isNoVisualMode()) {
4171         setAnchor();
4172         g.opcount = g.mvcount;
4173         g.mvcount = 0;
4174         g.rangemode = RangeCharMode;
4175         g.movetype = MoveExclusive;
4176         g.submode = input.is('c') ? ChangeSubMode : DeleteSubMode;
4177     } else if ((input.is('c') || input.is('C') || input.is('s') || input.is('R'))
4178           && (isVisualCharMode() || isVisualLineMode())) {
4179         setDotCommand(visualDotCommand() + input.asChar());
4180         leaveVisualMode();
4181         g.submode = ChangeSubMode;
4182         finishMovement();
4183     } else if ((input.is('c') || input.is('s')) && isVisualBlockMode()) {
4184         resetCount();
4185         enterVisualInsertMode(input.asChar());
4186     } else if (input.is('C')) {
4187         setAnchor();
4188         moveToEndOfLine();
4189         g.rangemode = RangeCharMode;
4190         g.submode = ChangeSubMode;
4191         setDotCommand(QString(QLatin1Char('C')));
4192         finishMovement();
4193     } else if (input.isControl('c')) {
4194         if (isNoVisualMode())
4195             showMessage(MessageInfo, tr("Type Alt-V, Alt-V to quit FakeVim mode."));
4196         else
4197             leaveVisualMode();
4198     } else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete))
4199             && isVisualMode()) {
4200         pushUndoState();
4201         setDotCommand(visualDotCommand() + QLatin1Char('x'));
4202         if (isVisualCharMode()) {
4203             leaveVisualMode();
4204             g.submode = DeleteSubMode;
4205             finishMovement();
4206         } else if (isVisualLineMode()) {
4207             leaveVisualMode();
4208             yankText(currentRange(), m_register);
4209             removeText(currentRange());
4210             handleStartOfLine();
4211         } else if (isVisualBlockMode()) {
4212             leaveVisualMode();
4213             yankText(currentRange(), m_register);
4214             removeText(currentRange());
4215             setPosition(qMin(position(), anchor()));
4216         }
4217     } else if (input.is('D') && isNoVisualMode()) {
4218         pushUndoState();
4219         if (atEndOfLine())
4220             moveLeft();
4221         g.submode = DeleteSubMode;
4222         g.movetype = MoveInclusive;
4223         setAnchorAndPosition(position(), lastPositionInLine(cursorLine() + count()));
4224         setDotCommand(QString(QLatin1Char('D')));
4225         finishMovement();
4226         setTargetColumn();
4227     } else if ((input.is('D') || input.is('X')) &&
4228          (isVisualCharMode() || isVisualLineMode())) {
4229         setDotCommand(visualDotCommand() + QLatin1Char('X'));
4230         leaveVisualMode();
4231         g.rangemode = RangeLineMode;
4232         g.submode = NoSubMode;
4233         yankText(currentRange(), m_register);
4234         removeText(currentRange());
4235         moveToFirstNonBlankOnLine();
4236     } else if ((input.is('D') || input.is('X')) && isVisualBlockMode()) {
4237         setDotCommand(visualDotCommand() + QLatin1Char('X'));
4238         leaveVisualMode();
4239         g.rangemode = RangeBlockAndTailMode;
4240         yankText(currentRange(), m_register);
4241         removeText(currentRange());
4242         setPosition(qMin(position(), anchor()));
4243     } else if (input.isControl('d')) {
4244         const int scrollOffset = windowScrollOffset();
4245         int sline = cursorLine() < scrollOffset ? scrollOffset : cursorLineOnScreen();
4246         // FIXME: this should use the "scroll" option, and "count"
4247         moveDown(linesOnScreen() / 2);
4248         handleStartOfLine();
4249         scrollToLine(cursorLine() - sline);
4250     } else if (!g.gflag && input.is('g')) {
4251         g.gflag = true;
4252     } else if (!isVisualMode() && (input.is('i') || input.isKey(Key_Insert))) {
4253         setDotCommand(_("%1i"), count());
4254         breakEditBlock();
4255         enterInsertMode();
4256         if (atEndOfLine())
4257             moveLeft();
4258     } else if (input.is('I')) {
4259         if (isVisualMode()) {
4260             enterVisualInsertMode(QLatin1Char('I'));
4261         } else {
4262             if (g.gflag) {
4263                 setDotCommand(_("%1gI"), count());
4264                 moveToStartOfLine();
4265             } else {
4266                 setDotCommand(_("%1I"), count());
4267                 moveToFirstNonBlankOnLine();
4268             }
4269             breakEditBlock();
4270             enterInsertMode();
4271         }
4272     } else if (input.isControl('i')) {
4273         jump(count());
4274     } else if (input.is('J')) {
4275         pushUndoState();
4276         moveBehindEndOfLine();
4277         beginEditBlock();
4278         if (g.submode == NoSubMode)
4279             joinLines(count(), g.gflag);
4280         endEditBlock();
4281         setDotCommand(_("%1J"), count());
4282     } else if (input.isControl('l')) {
4283         // screen redraw. should not be needed
4284     } else if (!g.gflag && input.is('m')) {
4285         g.subsubmode = MarkSubSubMode;
4286     } else if (isVisualMode() && (input.is('o') || input.is('O'))) {
4287         int pos = position();
4288         setAnchorAndPosition(pos, anchor());
4289         std::swap(m_positionPastEnd, m_anchorPastEnd);
4290         setTargetColumn();
4291         if (m_positionPastEnd)
4292             m_visualTargetColumn = -1;
4293     } else if (input.is('o') || input.is('O')) {
4294         bool insertAfter = input.is('o');
4295         setDotCommand(_(insertAfter ? "%1o" : "%1O"), count());
4296         pushUndoState();
4297 
4298         // Prepend line only if on the first line and command is 'O'.
4299         bool appendLine = true;
4300         if (!insertAfter) {
4301             if (block().blockNumber() == 0)
4302                 appendLine = false;
4303             else
4304                 moveUp();
4305         }
4306         const int line = lineNumber(block());
4307 
4308         beginEditBlock();
4309         enterInsertMode();
4310         setPosition(appendLine ? lastPositionInLine(line) : firstPositionInLine(line));
4311         clearLastInsertion();
4312         setAnchor();
4313         insertNewLine();
4314         if (appendLine) {
4315             m_buffer->insertState.newLineBefore = true;
4316         } else {
4317             moveUp();
4318             m_oldInternalPosition = position();
4319             m_buffer->insertState.pos1 = m_oldInternalPosition;
4320             m_buffer->insertState.newLineAfter = true;
4321         }
4322         setTargetColumn();
4323         endEditBlock();
4324 
4325         // Close accidentally opened block.
4326         if (block().blockNumber() > 0) {
4327             moveUp();
4328             if (line != lineNumber(block()))
4329                 emit q->fold(1, true);
4330             moveDown();
4331         }
4332     } else if (input.isControl('o')) {
4333         jump(-count());
4334     } else if (input.is('p') || input.is('P') || input.isShift(Qt::Key_Insert)) {
4335         pasteText(!input.is('P'));
4336         setTargetColumn();
4337         setDotCommand(_("%1p"), count());
4338         finishMovement();
4339     } else if (input.is('q')) {
4340         if (g.recording.isNull()) {
4341             // Recording shouldn't work in mapping or while executing register.
4342             handled = g.mapStates.empty();
4343             if (handled)
4344                 g.submode = MacroRecordSubMode;
4345         } else {
4346             // Stop recording.
4347             stopRecording();
4348         }
4349     } else if (input.is('r')) {
4350         g.submode = ReplaceSubMode;
4351     } else if (!isVisualMode() && input.is('R')) {
4352         pushUndoState();
4353         breakEditBlock();
4354         enterReplaceMode();
4355     } else if (input.isControl('r')) {
4356         int repeat = count();
4357         while (--repeat >= 0)
4358             redo();
4359     } else if (input.is('s')) {
4360         pushUndoState();
4361         leaveVisualMode();
4362         if (atEndOfLine())
4363             moveLeft();
4364         setAnchor();
4365         moveRight(qMin(count(), rightDist()));
4366         setDotCommand(_("%1s"), count());
4367         g.submode = ChangeSubMode;
4368         g.movetype = MoveExclusive;
4369         finishMovement();
4370     } else if (input.is('S')) {
4371         g.movetype = MoveLineWise;
4372         pushUndoState();
4373         if (!isVisualMode()) {
4374             const int line = cursorLine() + 1;
4375             const int anc = firstPositionInLine(line);
4376             const int pos = lastPositionInLine(line + count() - 1);
4377             setAnchorAndPosition(anc, pos);
4378         }
4379         setDotCommand(_("%1S"), count());
4380         g.submode = ChangeSubMode;
4381         finishMovement();
4382     } else if (g.gflag && input.is('t')) {
4383         handleExCommand(_("tabnext"));
4384     } else if (g.gflag && input.is('T')) {
4385         handleExCommand(_("tabprev"));
4386     } else if (input.isControl('t')) {
4387         handleExCommand(_("pop"));
4388     } else if (!g.gflag && input.is('u') && !isVisualMode()) {
4389         int repeat = count();
4390         while (--repeat >= 0)
4391             undo();
4392     } else if (input.isControl('u')) {
4393         int sline = cursorLineOnScreen();
4394         // FIXME: this should use the "scroll" option, and "count"
4395         moveUp(linesOnScreen() / 2);
4396         handleStartOfLine();
4397         scrollToLine(cursorLine() - sline);
4398     } else if (g.gflag && input.is('v')) {
4399         if (m_buffer->lastVisualMode != NoVisualMode) {
4400             CursorPosition from = markLessPosition();
4401             CursorPosition to = markGreaterPosition();
4402             toggleVisualMode(m_buffer->lastVisualMode);
4403             setCursorPosition(m_buffer->lastVisualModeInverted ? to : from);
4404             setAnchor();
4405             setCursorPosition(m_buffer->lastVisualModeInverted ? from : to);
4406             setTargetColumn();
4407         }
4408     } else if (input.is('v')) {
4409         toggleVisualMode(VisualCharMode);
4410     } else if (input.is('V')) {
4411         toggleVisualMode(VisualLineMode);
4412     } else if (input.isControl('v')) {
4413         toggleVisualMode(VisualBlockMode);
4414     } else if (input.isControl('w')) {
4415         g.submode = WindowSubMode;
4416     } else if (input.is('x') && isNoVisualMode()) { // = _("dl")
4417         g.movetype = MoveExclusive;
4418         g.submode = DeleteSubMode;
4419         const int n = qMin(count(), rightDist());
4420         setAnchorAndPosition(position(), position() + n);
4421         setDotCommand(_("%1x"), count());
4422         finishMovement();
4423     } else if (input.isControl('x')) {
4424         if (changeNumberTextObject(-count()))
4425             setDotCommand(_("%1<c-x>"), count());
4426     } else if (input.is('X')) {
4427         if (leftDist() > 0) {
4428             setAnchor();
4429             moveLeft(qMin(count(), leftDist()));
4430             yankText(currentRange(), m_register);
4431             removeText(currentRange());
4432         }
4433     } else if (input.is('Y') && isNoVisualMode())  {
4434         handleYankSubMode(Input(QLatin1Char('y')));
4435     } else if (input.isControl('y')) {
4436         // FIXME: this should use the "scroll" option, and "count"
4437         if (cursorLineOnScreen() == linesOnScreen() - 1)
4438             moveUp(1);
4439         scrollUp(1);
4440     } else if (input.is('y') && isNoVisualMode()) {
4441         setAnchor();
4442         g.rangemode = RangeCharMode;
4443         g.movetype = MoveExclusive;
4444         g.submode = YankSubMode;
4445     } else if (input.is('y') && isVisualCharMode()) {
4446         g.rangemode = RangeCharMode;
4447         g.movetype = MoveInclusive;
4448         g.submode = YankSubMode;
4449         finishMovement();
4450     } else if ((input.is('y') && isVisualLineMode())
4451             || (input.is('Y') && isVisualLineMode())
4452             || (input.is('Y') && isVisualCharMode())) {
4453         g.rangemode = RangeLineMode;
4454         g.movetype = MoveLineWise;
4455         g.submode = YankSubMode;
4456         finishMovement();
4457     } else if ((input.is('y') || input.is('Y')) && isVisualBlockMode()) {
4458         g.rangemode = RangeBlockMode;
4459         g.movetype = MoveInclusive;
4460         g.submode = YankSubMode;
4461         finishMovement();
4462     } else if (input.is('z')) {
4463         g.submode = ZSubMode;
4464     } else if (input.is('Z')) {
4465         g.submode = CapitalZSubMode;
4466     } else if ((input.is('~') || input.is('u') || input.is('U'))) {
4467         g.movetype = MoveExclusive;
4468         pushUndoState();
4469         if (isVisualMode()) {
4470             setDotCommand(visualDotCommand() + QString::number(count()) + input.raw());
4471             if (isVisualLineMode())
4472                 g.rangemode = RangeLineMode;
4473             else if (isVisualBlockMode())
4474                 g.rangemode = RangeBlockMode;
4475             leaveVisualMode();
4476             if (input.is('~'))
4477                 g.submode = InvertCaseSubMode;
4478             else if (input.is('u'))
4479                 g.submode = DownCaseSubMode;
4480             else if (input.is('U'))
4481                 g.submode = UpCaseSubMode;
4482             finishMovement();
4483         } else if (g.gflag || (input.is('~') && hasConfig(ConfigTildeOp))) {
4484             if (atEndOfLine())
4485                 moveLeft();
4486             setAnchor();
4487             if (input.is('~'))
4488                 g.submode = InvertCaseSubMode;
4489             else if (input.is('u'))
4490                 g.submode = DownCaseSubMode;
4491             else if (input.is('U'))
4492                 g.submode = UpCaseSubMode;
4493         } else {
4494             beginEditBlock();
4495             if (atEndOfLine())
4496                 moveLeft();
4497             setAnchor();
4498             moveRight(qMin(count(), rightDist()));
4499             if (input.is('~')) {
4500                 const int pos = position();
4501                 invertCase(currentRange());
4502                 setPosition(pos);
4503             } else if (input.is('u')) {
4504                 downCase(currentRange());
4505             } else if (input.is('U')) {
4506                 upCase(currentRange());
4507             }
4508             setDotCommand(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
4509             endEditBlock();
4510         }
4511     } else if (input.is('@')) {
4512         g.submode = MacroExecuteSubMode;
4513     } else if (input.isKey(Key_Delete)) {
4514         setAnchor();
4515         moveRight(qMin(1, rightDist()));
4516         removeText(currentRange());
4517         if (atEndOfLine())
4518             moveLeft();
4519     } else if (input.isControl(Key_BracketRight)) {
4520         handleExCommand(_("tag"));
4521     } else if (handleMovement(input)) {
4522         // movement handled
4523     } else {
4524         handled = false;
4525     }
4526 
4527     return handled;
4528 }
4529 
handleChangeDeleteSubModes(const Input & input)4530 bool FakeVimHandler::Private::handleChangeDeleteSubModes(const Input &input)
4531 {
4532     bool handled = false;
4533 
4534     if ((g.submode == ChangeSubMode && input.is('c'))
4535         || (g.submode == DeleteSubMode && input.is('d'))) {
4536         g.movetype = MoveLineWise;
4537         pushUndoState();
4538         const int anc = firstPositionInLine(cursorLine() + 1);
4539         moveDown(count() - 1);
4540         const int pos = lastPositionInLine(cursorLine() + 1);
4541         setAnchorAndPosition(anc, pos);
4542         if (g.submode == ChangeSubMode)
4543             setDotCommand(_("%1cc"), count());
4544         else
4545             setDotCommand(_("%1dd"), count());
4546         finishMovement();
4547         g.submode = NoSubMode;
4548         handled = true;
4549     }
4550 
4551     return handled;
4552 }
4553 
handleReplaceSubMode(const Input & input)4554 bool FakeVimHandler::Private::handleReplaceSubMode(const Input &input)
4555 {
4556     bool handled = true;
4557 
4558     setDotCommand(visualDotCommand() + QLatin1Char('r') + input.asChar());
4559     if (isVisualMode()) {
4560         pushUndoState();
4561         if (isVisualLineMode())
4562             g.rangemode = RangeLineMode;
4563         else if (isVisualBlockMode())
4564             g.rangemode = RangeBlockMode;
4565         else
4566             g.rangemode = RangeCharMode;
4567         leaveVisualMode();
4568         Range range = currentRange();
4569         if (g.rangemode == RangeCharMode)
4570             ++range.endPos;
4571         Transformation tr =
4572                 &FakeVimHandler::Private::replaceByCharTransform;
4573         transformText(range, tr, input.asChar());
4574     } else if (count() <= rightDist()) {
4575         pushUndoState();
4576         setAnchor();
4577         moveRight(count());
4578         Range range = currentRange();
4579         if (input.isReturn()) {
4580             beginEditBlock();
4581             replaceText(range, QString());
4582             insertText(QString::fromLatin1("\n"));
4583             endEditBlock();
4584         } else {
4585             replaceText(range, QString(count(), input.asChar()));
4586             moveRight(count() - 1);
4587         }
4588         setTargetColumn();
4589         setDotCommand(_("%1r") + input.text(), count());
4590     } else {
4591         handled = false;
4592     }
4593     g.submode = NoSubMode;
4594     finishMovement();
4595 
4596     return handled;
4597 }
4598 
handleFilterSubMode(const Input &)4599 bool FakeVimHandler::Private::handleFilterSubMode(const Input &)
4600 {
4601     return false;
4602 }
4603 
handleRegisterSubMode(const Input & input)4604 bool FakeVimHandler::Private::handleRegisterSubMode(const Input &input)
4605 {
4606     bool handled = false;
4607 
4608     QChar reg = input.asChar();
4609     if (QString::fromLatin1("*+.%#:-\"").contains(reg) || reg.isLetterOrNumber()) {
4610         m_register = reg.unicode();
4611         g.rangemode = RangeLineMode;
4612         handled = true;
4613     }
4614     g.submode = NoSubMode;
4615 
4616     return handled;
4617 }
4618 
handleShiftSubMode(const Input & input)4619 bool FakeVimHandler::Private::handleShiftSubMode(const Input &input)
4620 {
4621     bool handled = false;
4622     if ((g.submode == ShiftLeftSubMode && input.is('<'))
4623         || (g.submode == ShiftRightSubMode && input.is('>'))
4624         || (g.submode == IndentSubMode && input.is('='))) {
4625         g.movetype = MoveLineWise;
4626         pushUndoState();
4627         moveDown(count() - 1);
4628         setDotCommand(QString::fromLatin1("%2%1%1").arg(input.asChar()), count());
4629         finishMovement();
4630         handled = true;
4631         g.submode = NoSubMode;
4632     }
4633     return handled;
4634 }
4635 
handleChangeCaseSubMode(const Input & input)4636 bool FakeVimHandler::Private::handleChangeCaseSubMode(const Input &input)
4637 {
4638     bool handled = false;
4639     if ((g.submode == InvertCaseSubMode && input.is('~'))
4640         || (g.submode == DownCaseSubMode && input.is('u'))
4641         || (g.submode == UpCaseSubMode && input.is('U'))) {
4642         if (!isFirstNonBlankOnLine(position())) {
4643             moveToStartOfLine();
4644             moveToFirstNonBlankOnLine();
4645         }
4646         setTargetColumn();
4647         pushUndoState();
4648         setAnchor();
4649         setPosition(lastPositionInLine(cursorLine() + count()) + 1);
4650         finishMovement(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
4651         handled = true;
4652         g.submode = NoSubMode;
4653     }
4654     return handled;
4655 }
4656 
handleWindowSubMode(const Input & input)4657 bool FakeVimHandler::Private::handleWindowSubMode(const Input &input)
4658 {
4659     if (handleCount(input))
4660         return true;
4661 
4662     leaveVisualMode();
4663     emit q->windowCommandRequested(input.toString(), count());
4664 
4665     g.submode = NoSubMode;
4666     return true;
4667 }
4668 
handleYankSubMode(const Input & input)4669 bool FakeVimHandler::Private::handleYankSubMode(const Input &input)
4670 {
4671     bool handled = false;
4672     if (input.is('y')) {
4673         g.movetype = MoveLineWise;
4674         int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
4675         Range range(position(), endPos, RangeLineMode);
4676         yankText(range, m_register);
4677         g.submode = NoSubMode;
4678         handled = true;
4679     }
4680     return handled;
4681 }
4682 
handleZSubMode(const Input & input)4683 bool FakeVimHandler::Private::handleZSubMode(const Input &input)
4684 {
4685     bool handled = true;
4686     bool foldMaybeClosed = false;
4687     if (input.isReturn() || input.is('t')
4688         || input.is('-') || input.is('b')
4689         || input.is('.') || input.is('z')) {
4690         // Cursor line to top/center/bottom of window.
4691         Qt::AlignmentFlag align;
4692         if (input.isReturn() || input.is('t'))
4693             align = Qt::AlignTop;
4694         else if (input.is('.') || input.is('z'))
4695             align = Qt::AlignVCenter;
4696         else
4697             align = Qt::AlignBottom;
4698         const bool moveToNonBlank = (input.is('.') || input.isReturn() || input.is('-'));
4699         const int line = g.mvcount == 0 ? -1 : firstPositionInLine(count());
4700         alignViewportToCursor(align, line, moveToNonBlank);
4701     } else if (input.is('o') || input.is('c')) {
4702         // Open/close current fold.
4703         foldMaybeClosed = input.is('c');
4704         emit q->fold(count(), foldMaybeClosed);
4705     } else if (input.is('O') || input.is('C')) {
4706         // Recursively open/close current fold.
4707         foldMaybeClosed = input.is('C');
4708         emit q->fold(-1, foldMaybeClosed);
4709     } else if (input.is('a') || input.is('A')) {
4710         // Toggle current fold.
4711         foldMaybeClosed = true;
4712         emit q->foldToggle(input.is('a') ? count() : -1);
4713     } else if (input.is('R') || input.is('M')) {
4714         // Open/close all folds in document.
4715         foldMaybeClosed = input.is('M');
4716         emit q->foldAll(foldMaybeClosed);
4717     } else if (input.is('j') || input.is('k')) {
4718         emit q->foldGoTo(input.is('j') ? count() : -count(), false);
4719     } else {
4720         handled = false;
4721     }
4722     if (foldMaybeClosed)
4723         ensureCursorVisible();
4724     g.submode = NoSubMode;
4725     return handled;
4726 }
4727 
handleCapitalZSubMode(const Input & input)4728 bool FakeVimHandler::Private::handleCapitalZSubMode(const Input &input)
4729 {
4730     // Recognize ZZ and ZQ as aliases for ":x" and ":q!".
4731     bool handled = true;
4732     if (input.is('Z'))
4733         handleExCommand(QString(QLatin1Char('x')));
4734     else if (input.is('Q'))
4735         handleExCommand(_("q!"));
4736     else
4737         handled = false;
4738     g.submode = NoSubMode;
4739     return handled;
4740 }
4741 
handleMacroRecordSubMode(const Input & input)4742 bool FakeVimHandler::Private::handleMacroRecordSubMode(const Input &input)
4743 {
4744     g.submode = NoSubMode;
4745     return startRecording(input);
4746 }
4747 
handleMacroExecuteSubMode(const Input & input)4748 bool FakeVimHandler::Private::handleMacroExecuteSubMode(const Input &input)
4749 {
4750     g.submode = NoSubMode;
4751 
4752     bool result = true;
4753     int repeat = count();
4754     while (result && --repeat >= 0)
4755         result = executeRegister(input.asChar().unicode());
4756 
4757     return result;
4758 }
4759 
handleInsertOrReplaceMode(const Input & input)4760 EventResult FakeVimHandler::Private::handleInsertOrReplaceMode(const Input &input)
4761 {
4762     if (position() < m_buffer->insertState.pos1 || position() > m_buffer->insertState.pos2) {
4763         commitInsertState();
4764         invalidateInsertState();
4765     }
4766 
4767     if (g.mode == InsertMode)
4768         handleInsertMode(input);
4769     else
4770         handleReplaceMode(input);
4771 
4772     if (!m_textedit && !m_plaintextedit)
4773         return EventHandled;
4774 
4775     if (!isInsertMode() || m_buffer->breakEditBlock
4776             || position() < m_buffer->insertState.pos1 || position() > m_buffer->insertState.pos2) {
4777         commitInsertState();
4778         invalidateInsertState();
4779         breakEditBlock();
4780         m_visualBlockInsert = NoneBlockInsertMode;
4781     } else if (m_oldInternalPosition == position()) {
4782         setTargetColumn();
4783     }
4784 
4785     updateMiniBuffer();
4786 
4787     // We don't want fancy stuff in insert mode.
4788     return EventHandled;
4789 }
4790 
handleReplaceMode(const Input & input)4791 void FakeVimHandler::Private::handleReplaceMode(const Input &input)
4792 {
4793     if (input.isEscape()) {
4794         commitInsertState();
4795         moveLeft(qMin(1, leftDist()));
4796         enterCommandMode();
4797         g.dotCommand.append(m_buffer->lastInsertion + _("<ESC>"));
4798     } else if (input.isKey(Key_Left)) {
4799         moveLeft();
4800         setTargetColumn();
4801     } else if (input.isKey(Key_Right)) {
4802         moveRight();
4803         setTargetColumn();
4804     } else if (input.isKey(Key_Up)) {
4805         moveUp();
4806     } else if (input.isKey(Key_Down)) {
4807         moveDown();
4808     } else if (input.isKey(Key_Insert)) {
4809         g.mode = InsertMode;
4810     } else if (input.isControl('o')) {
4811         enterCommandMode(ReplaceMode);
4812     } else {
4813         joinPreviousEditBlock();
4814         if (!atEndOfLine()) {
4815             setAnchor();
4816             moveRight();
4817             removeText(currentRange());
4818         }
4819         const QString text = input.text();
4820         setAnchor();
4821         insertText(text);
4822         endEditBlock();
4823     }
4824 }
4825 
finishInsertMode()4826 void FakeVimHandler::Private::finishInsertMode()
4827 {
4828     bool newLineAfter = m_buffer->insertState.newLineAfter;
4829     bool newLineBefore = m_buffer->insertState.newLineBefore;
4830 
4831     // Repeat insertion [count] times.
4832     // One instance was already physically inserted while typing.
4833     if (!m_buffer->breakEditBlock && isInsertStateValid()) {
4834         commitInsertState();
4835 
4836         QString text = m_buffer->lastInsertion;
4837         const QString dotCommand = g.dotCommand;
4838         const int repeat = count() - 1;
4839         m_buffer->lastInsertion.clear();
4840         joinPreviousEditBlock();
4841 
4842         if (newLineAfter) {
4843             text.chop(1);
4844             text.prepend(_("<END>\n"));
4845         } else if (newLineBefore) {
4846             text.prepend(_("<END>"));
4847         }
4848 
4849         replay(text, repeat);
4850 
4851         if (m_visualBlockInsert != NoneBlockInsertMode && !text.contains(QLatin1Char('\n'))) {
4852             const CursorPosition lastAnchor = markLessPosition();
4853             const CursorPosition lastPosition = markGreaterPosition();
4854             bool change = m_visualBlockInsert == ChangeBlockInsertMode;
4855             const int insertColumn = (m_visualBlockInsert == InsertBlockInsertMode || change)
4856                     ? qMin(lastPosition.column, lastAnchor.column)
4857                     : qMax(lastPosition.column, lastAnchor.column) + 1;
4858 
4859             CursorPosition pos(lastAnchor.line, insertColumn);
4860 
4861             if (change)
4862                 pos.column = m_buffer->insertState.pos1 - document()->findBlock(m_buffer->insertState.pos1).position();
4863 
4864             // Cursor position after block insert is on the first selected line,
4865             // last selected column for 's' command, otherwise first selected column.
4866             const int endColumn = change ? qMax(0, m_cursor.positionInBlock() - 1)
4867                                          : qMin(lastPosition.column, lastAnchor.column);
4868 
4869             while (pos.line < lastPosition.line) {
4870                 ++pos.line;
4871                 setCursorPosition(&m_cursor, pos);
4872                 if (m_visualBlockInsert == AppendToEndOfLineBlockInsertMode) {
4873                     moveToEndOfLine();
4874                 } else if (m_visualBlockInsert == AppendBlockInsertMode) {
4875                     // Prepend spaces if necessary.
4876                     int spaces = pos.column - m_cursor.positionInBlock();
4877                     if (spaces > 0) {
4878                         setAnchor();
4879                         m_cursor.insertText(QString(_(" ")).repeated(spaces));
4880                     }
4881                 } else if (m_cursor.positionInBlock() != pos.column) {
4882                     continue;
4883                 }
4884                 replay(text, repeat + 1);
4885             }
4886 
4887             setCursorPosition(CursorPosition(lastAnchor.line, endColumn));
4888         } else {
4889             moveLeft(qMin(1, leftDist()));
4890         }
4891 
4892         endEditBlock();
4893         breakEditBlock();
4894 
4895         m_buffer->lastInsertion = text;
4896         g.dotCommand = dotCommand;
4897     } else {
4898         moveLeft(qMin(1, leftDist()));
4899     }
4900 
4901     if (newLineBefore || newLineAfter)
4902         m_buffer->lastInsertion.remove(0, m_buffer->lastInsertion.indexOf(QLatin1Char('\n')) + 1);
4903     g.dotCommand.append(m_buffer->lastInsertion + _("<ESC>"));
4904 
4905     enterCommandMode();
4906     setTargetColumn();
4907 }
4908 
handleInsertMode(const Input & input)4909 void FakeVimHandler::Private::handleInsertMode(const Input &input)
4910 {
4911     if (input.isEscape()) {
4912         finishInsertMode();
4913     } else if (g.submode == CtrlVSubMode) {
4914         if (g.subsubmode == NoSubSubMode) {
4915             g.subsubmode = CtrlVUnicodeSubSubMode;
4916             m_ctrlVAccumulator = 0;
4917             if (input.is('x') || input.is('X')) {
4918                 // ^VXnn or ^Vxnn with 00 <= nn <= FF
4919                 // BMP Unicode codepoints ^Vunnnn with 0000 <= nnnn <= FFFF
4920                 // any Unicode codepoint ^VUnnnnnnnn with 00000000 <= nnnnnnnn <= 7FFFFFFF
4921                 // ^Vnnn with 000 <= nnn <= 255
4922                 // ^VOnnn or ^Vonnn with 000 <= nnn <= 377
4923                 m_ctrlVLength = 2;
4924                 m_ctrlVBase = 16;
4925             } else if (input.is('O') || input.is('o')) {
4926                 m_ctrlVLength = 3;
4927                 m_ctrlVBase = 8;
4928             } else if (input.is('u')) {
4929                 m_ctrlVLength = 4;
4930                 m_ctrlVBase = 16;
4931             } else if (input.is('U')) {
4932                 m_ctrlVLength = 8;
4933                 m_ctrlVBase = 16;
4934             } else if (input.isDigit()) {
4935                 bool ok;
4936                 m_ctrlVAccumulator = input.toInt(&ok, 10);
4937                 m_ctrlVLength = 2;
4938                 m_ctrlVBase = 10;
4939             } else {
4940                 insertInInsertMode(input.raw());
4941                 g.submode = NoSubMode;
4942                 g.subsubmode = NoSubSubMode;
4943             }
4944         } else {
4945             bool ok;
4946             int current = input.toInt(&ok, m_ctrlVBase);
4947             if (ok)
4948                 m_ctrlVAccumulator = m_ctrlVAccumulator * m_ctrlVBase + current;
4949             --m_ctrlVLength;
4950             if (m_ctrlVLength == 0 || !ok) {
4951                 QString s;
4952                 if (QChar::requiresSurrogates(m_ctrlVAccumulator)) {
4953                     s.append(QChar(QChar::highSurrogate(m_ctrlVAccumulator)));
4954                     s.append(QChar(QChar::lowSurrogate(m_ctrlVAccumulator)));
4955                 } else {
4956                     s.append(QChar(m_ctrlVAccumulator));
4957                 }
4958                 insertInInsertMode(s);
4959                 g.submode = NoSubMode;
4960                 g.subsubmode = NoSubSubMode;
4961 
4962                 // Try again without Ctrl-V interpretation.
4963                 if (!ok)
4964                     handleInsertMode(input);
4965             }
4966         }
4967     } else if (input.isControl('o')) {
4968         enterCommandMode(InsertMode);
4969     } else if (input.isControl('v')) {
4970         g.submode = CtrlVSubMode;
4971         g.subsubmode = NoSubSubMode;
4972     } else if (input.isControl('w')) {
4973         const int blockNumber = m_cursor.blockNumber();
4974         const int endPos = position();
4975         moveToNextWordStart(1, false, false);
4976         if (blockNumber != m_cursor.blockNumber())
4977             moveToEndOfLine();
4978         const int beginPos = position();
4979         Range range(beginPos, endPos, RangeCharMode);
4980         removeText(range);
4981     } else if (input.isKey(Key_Insert)) {
4982         g.mode = ReplaceMode;
4983     } else if (input.isKey(Key_Left)) {
4984         moveLeft();
4985         setTargetColumn();
4986     } else if (input.isControl(Key_Left)) {
4987         moveToNextWordStart(1, false, false);
4988         setTargetColumn();
4989     } else if (input.isKey(Key_Down)) {
4990         g.submode = NoSubMode;
4991         moveDown();
4992     } else if (input.isKey(Key_Up)) {
4993         g.submode = NoSubMode;
4994         moveUp();
4995     } else if (input.isKey(Key_Right)) {
4996         moveRight();
4997         setTargetColumn();
4998     } else if (input.isControl(Key_Right)) {
4999         moveToNextWordStart(1, false, true);
5000         moveRight(); // we need one more move since we are in insert mode
5001         setTargetColumn();
5002     } else if (input.isKey(Key_Home)) {
5003         moveToStartOfLine();
5004         setTargetColumn();
5005     } else if (input.isKey(Key_End)) {
5006         moveBehindEndOfLine();
5007         setTargetColumn();
5008         m_targetColumn = -1;
5009     } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) {
5010         if (!input.isReturn() || !handleInsertInEditor(input)) {
5011             joinPreviousEditBlock();
5012             g.submode = NoSubMode;
5013             insertNewLine();
5014             endEditBlock();
5015         }
5016     } else if (input.isBackspace()) {
5017         if (!handleInsertInEditor(input)) {
5018             joinPreviousEditBlock();
5019             if (!m_buffer->lastInsertion.isEmpty()
5020                     || hasConfig(ConfigBackspace, "start")
5021                     || hasConfig(ConfigBackspace, "2")) {
5022                 const int line = cursorLine() + 1;
5023                 const Column col = cursorColumn();
5024                 QString data = lineContents(line);
5025                 const Column ind = indentation(data);
5026                 if (col.logical <= ind.logical && col.logical
5027                         && startsWithWhitespace(data, col.physical)) {
5028                     const int ts = config(ConfigTabStop).toInt();
5029                     const int newl = col.logical - 1 - (col.logical - 1) % ts;
5030                     const QString prefix = tabExpand(newl);
5031                     setLineContents(line, prefix + data.mid(col.physical));
5032                     moveToStartOfLine();
5033                     moveRight(prefix.size());
5034                 } else {
5035                     setAnchor();
5036                     m_cursor.deletePreviousChar();
5037                 }
5038             }
5039             endEditBlock();
5040         }
5041     } else if (input.isKey(Key_Delete)) {
5042         if (!handleInsertInEditor(input)) {
5043             joinPreviousEditBlock();
5044             m_cursor.deleteChar();
5045             endEditBlock();
5046         }
5047     } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
5048         movePageDown();
5049     } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
5050         movePageUp();
5051     } else if (input.isKey(Key_Tab)) {
5052         m_buffer->insertState.insertingSpaces = true;
5053         if (hasConfig(ConfigExpandTab)) {
5054             const int ts = config(ConfigTabStop).toInt();
5055             const int col = logicalCursorColumn();
5056             QString str = QString(ts - col % ts, QLatin1Char(' '));
5057             insertText(str);
5058         } else {
5059             insertInInsertMode(input.raw());
5060         }
5061         m_buffer->insertState.insertingSpaces = false;
5062     } else if (input.isControl('d')) {
5063         // remove one level of indentation from the current line
5064         int shift = config(ConfigShiftWidth).toInt();
5065         int tab = config(ConfigTabStop).toInt();
5066         int line = cursorLine() + 1;
5067         int pos = firstPositionInLine(line);
5068         QString text = lineContents(line);
5069         int amount = 0;
5070         int i = 0;
5071         for (; i < text.size() && amount < shift; ++i) {
5072             if (text.at(i) == QLatin1Char(' '))
5073                 ++amount;
5074             else if (text.at(i) == QLatin1Char('\t'))
5075                 amount += tab; // FIXME: take position into consideration
5076             else
5077                 break;
5078         }
5079         removeText(Range(pos, pos+i));
5080     } else if (input.isControl('p') || input.isControl('n')) {
5081         QTextCursor tc = m_cursor;
5082         moveToNextWordStart(1, false, false);
5083         QString str = selectText(Range(position(), tc.position()));
5084         m_cursor = tc;
5085         emit q->simpleCompletionRequested(str, input.isControl('n'));
5086     } else if (input.isShift(Qt::Key_Insert)) {
5087         // Insert text from clipboard.
5088         QClipboard *clipboard = QApplication::clipboard();
5089         const QMimeData *data = clipboard->mimeData();
5090         if (data && data->hasText())
5091             insertInInsertMode(data->text());
5092     } else {
5093         m_buffer->insertState.insertingSpaces = input.isKey(Key_Space);
5094         if (!handleInsertInEditor(input)) {
5095             const QString toInsert = input.text();
5096             if (toInsert.isEmpty())
5097                 return;
5098             insertInInsertMode(toInsert);
5099         }
5100         m_buffer->insertState.insertingSpaces = false;
5101     }
5102 }
5103 
insertInInsertMode(const QString & text)5104 void FakeVimHandler::Private::insertInInsertMode(const QString &text)
5105 {
5106     joinPreviousEditBlock();
5107     insertText(text);
5108     if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
5109         const QString leftText = block().text()
5110                .left(position() - 1 - block().position());
5111         if (leftText.simplified().isEmpty()) {
5112             Range range(position(), position(), g.rangemode);
5113             indentText(range, text.at(0));
5114         }
5115     }
5116     setTargetColumn();
5117     endEditBlock();
5118     g.submode = NoSubMode;
5119 }
5120 
startRecording(const Input & input)5121 bool FakeVimHandler::Private::startRecording(const Input &input)
5122 {
5123     QChar reg = input.asChar();
5124     if (reg == QLatin1Char('"') || reg.isLetterOrNumber()) {
5125         g.currentRegister = reg.unicode();
5126         g.recording = QLatin1String("");
5127         return true;
5128     }
5129 
5130     return false;
5131 }
5132 
record(const Input & input)5133 void FakeVimHandler::Private::record(const Input &input)
5134 {
5135     if ( !g.recording.isNull() )
5136         g.recording.append(input.toString());
5137 }
5138 
stopRecording()5139 void FakeVimHandler::Private::stopRecording()
5140 {
5141     // Remove q from end (stop recording command).
5142     g.recording.remove(g.recording.size() - 1, 1);
5143     setRegister(g.currentRegister, g.recording, g.rangemode);
5144     g.currentRegister = 0;
5145     g.recording = QString();
5146 }
5147 
executeRegister(int reg)5148 bool FakeVimHandler::Private::executeRegister(int reg)
5149 {
5150     QChar regChar(reg);
5151 
5152     // TODO: Prompt for an expression to execute if register is '='.
5153     if (reg == '@' && g.lastExecutedRegister != 0)
5154         reg = g.lastExecutedRegister;
5155     else if (QString::fromLatin1("\".*+").contains(regChar) || regChar.isLetterOrNumber())
5156         g.lastExecutedRegister = reg;
5157     else
5158         return false;
5159 
5160     // FIXME: In Vim it's possible to interrupt recursive macro with <C-c>.
5161     //        One solution may be to call QApplication::processEvents() and check if <C-c> was
5162     //        used when a mapping is active.
5163     // According to Vim, register is executed like mapping.
5164     prependMapping(Inputs(registerContents(reg), false, false));
5165 
5166     return true;
5167 }
5168 
handleExMode(const Input & input)5169 EventResult FakeVimHandler::Private::handleExMode(const Input &input)
5170 {
5171     if (input.isEscape()) {
5172         g.commandBuffer.clear();
5173         resetCommandMode();
5174         g.submode = NoSubMode;
5175     } else if (g.submode == CtrlVSubMode) {
5176         g.commandBuffer.insertChar(input.raw());
5177         g.submode = NoSubMode;
5178     } else if (input.isControl('v')) {
5179         g.submode = CtrlVSubMode;
5180         g.subsubmode = NoSubSubMode;
5181         return EventHandled;
5182     } else if (input.isBackspace()) {
5183         if (g.commandBuffer.isEmpty()) {
5184             leaveVisualMode();
5185             resetCommandMode();
5186         } else if (g.commandBuffer.hasSelection()) {
5187             g.commandBuffer.deleteSelected();
5188         } else {
5189             g.commandBuffer.deleteChar();
5190         }
5191     } else if (input.isKey(Key_Tab)) {
5192         // FIXME: Complete actual commands.
5193         g.commandBuffer.historyUp();
5194     } else if (input.isReturn()) {
5195         showMessage(MessageCommand, g.commandBuffer.display());
5196         handleExCommand(g.commandBuffer.contents());
5197         g.commandBuffer.clear();
5198         if (m_textedit || m_plaintextedit)
5199             leaveVisualMode();
5200     } else if (!g.commandBuffer.handleInput(input)) {
5201         qDebug() << "IGNORED IN EX-MODE: " << input.key() << input.text();
5202         return EventUnhandled;
5203     }
5204     updateMiniBuffer();
5205     return EventHandled;
5206 }
5207 
handleSearchSubSubMode(const Input & input)5208 EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
5209 {
5210     EventResult handled = EventHandled;
5211 
5212     if (input.isEscape()) {
5213         g.currentMessage.clear();
5214         setPosition(m_searchStartPosition);
5215         scrollToLine(m_searchFromScreenLine);
5216     } else if (input.isBackspace()) {
5217         if (g.searchBuffer.isEmpty())
5218             resetCommandMode();
5219         else
5220             g.searchBuffer.deleteChar();
5221     } else if (input.isReturn()) {
5222         const QString &needle = g.searchBuffer.contents();
5223         if (!needle.isEmpty())
5224             g.lastSearch = needle;
5225         else
5226             g.searchBuffer.setContents(g.lastSearch);
5227 
5228         updateFind(true);
5229 
5230         if (finishSearch()) {
5231             if (g.submode != NoSubMode)
5232                 finishMovement(g.searchBuffer.prompt() + g.lastSearch + QLatin1Char('\n'));
5233             if (g.currentMessage.isEmpty())
5234                 showMessage(MessageCommand, g.searchBuffer.display());
5235         } else {
5236             handled = EventCancelled; // Not found so cancel mapping if any.
5237         }
5238     } else if (input.isKey(Key_Tab)) {
5239         g.searchBuffer.insertChar(QChar(9));
5240     } else if (!g.searchBuffer.handleInput(input)) {
5241         //qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
5242         return EventUnhandled;
5243     }
5244 
5245     if (input.isReturn() || input.isEscape()) {
5246         g.searchBuffer.clear();
5247         resetCommandMode();
5248         updateMiniBuffer();
5249     } else {
5250         updateMiniBuffer();
5251         updateFind(false);
5252     }
5253 
5254     return handled;
5255 }
5256 
5257 // This uses 0 based line counting (hidden lines included).
parseLineAddress(QString * cmd)5258 int FakeVimHandler::Private::parseLineAddress(QString *cmd)
5259 {
5260     //qDebug() << "CMD: " << cmd;
5261     if (cmd->isEmpty())
5262         return -1;
5263 
5264     int result = -1;
5265     QChar c = cmd->at(0);
5266     if (c == QLatin1Char('.')) { // current line
5267         result = cursorBlockNumber();
5268         cmd->remove(0, 1);
5269     } else if (c == QLatin1Char('$')) { // last line
5270         result = document()->blockCount() - 1;
5271         cmd->remove(0, 1);
5272     } else if (c == QLatin1Char('\'')) { // mark
5273         cmd->remove(0, 1);
5274         if (cmd->isEmpty()) {
5275             showMessage(MessageError, msgMarkNotSet(QString()));
5276             return -1;
5277         }
5278         c = cmd->at(0);
5279         Mark m = mark(c);
5280         if (!m.isValid() || !m.isLocal(m_currentFileName)) {
5281             showMessage(MessageError, msgMarkNotSet(c));
5282             return -1;
5283         }
5284         cmd->remove(0, 1);
5285         result = m.position(document()).line;
5286     } else if (c.isDigit()) { // line with given number
5287         result = 0;
5288     } else if (c == QLatin1Char('-') || c == QLatin1Char('+')) { // add or subtract from current line number
5289         result = cursorBlockNumber();
5290     } else if (c == QLatin1Char('/') || c == QLatin1Char('?')
5291         || (c == QLatin1Char('\\') && cmd->size() > 1 && QString::fromLatin1("/?&").contains(cmd->at(1)))) {
5292         // search for expression
5293         SearchData sd;
5294         if (c == QLatin1Char('/') || c == QLatin1Char('?')) {
5295             const int end = findUnescaped(c, *cmd, 1);
5296             if (end == -1)
5297                 return -1;
5298             sd.needle = cmd->mid(1, end - 1);
5299             cmd->remove(0, end + 1);
5300         } else {
5301             c = cmd->at(1);
5302             cmd->remove(0, 2);
5303             sd.needle = (c == QLatin1Char('&')) ? g.lastSubstitutePattern : g.lastSearch;
5304         }
5305         sd.forward = (c != QLatin1Char('?'));
5306         const QTextBlock b = block();
5307         const int pos = b.position() + (sd.forward ? b.length() - 1 : 0);
5308         QTextCursor tc = search(sd, pos, 1, true);
5309         g.lastSearch = sd.needle;
5310         if (tc.isNull())
5311             return -1;
5312         result = tc.block().blockNumber();
5313     } else {
5314         return cursorBlockNumber();
5315     }
5316 
5317     // basic arithmetic ("-3+5" or "++" means "+2" etc.)
5318     int n = 0;
5319     bool add = true;
5320     int i = 0;
5321     for (; i < cmd->size(); ++i) {
5322         c = cmd->at(i);
5323         if (c == QLatin1Char('-') || c == QLatin1Char('+')) {
5324             if (n != 0)
5325                 result = result + (add ? n - 1 : -(n - 1));
5326             add = c == QLatin1Char('+');
5327             result = result + (add ? 1 : -1);
5328             n = 0;
5329         } else if (c.isDigit()) {
5330             n = n * 10 + c.digitValue();
5331         } else if (!c.isSpace()) {
5332             break;
5333         }
5334     }
5335     if (n != 0)
5336         result = result + (add ? n - 1 : -(n - 1));
5337     *cmd = cmd->mid(i).trimmed();
5338 
5339     return result;
5340 }
5341 
setCurrentRange(const Range & range)5342 void FakeVimHandler::Private::setCurrentRange(const Range &range)
5343 {
5344     setAnchorAndPosition(range.beginPos, range.endPos);
5345     g.rangemode = range.rangemode;
5346 }
5347 
parseExCommmand(QString * line,ExCommand * cmd)5348 bool FakeVimHandler::Private::parseExCommmand(QString *line, ExCommand *cmd)
5349 {
5350     *cmd = ExCommand();
5351     if (line->isEmpty())
5352         return false;
5353 
5354     // parse range first
5355     if (!parseLineRange(line, cmd))
5356         return false;
5357 
5358     // get first command from command line
5359     QChar close;
5360     bool subst = false;
5361     int i = 0;
5362     for (; i < line->size(); ++i) {
5363         const QChar &c = line->at(i);
5364         if (c == QLatin1Char('\\')) {
5365             ++i; // skip escaped character
5366         } else if (close.isNull()) {
5367             if (c == QLatin1Char('|')) {
5368                 // split on |
5369                 break;
5370             } else if (c == QLatin1Char('/')) {
5371                 subst = i > 0 && (line->at(i - 1) == QLatin1Char('s'));
5372                 close = c;
5373             } else if (c == QLatin1Char('"') || c == QLatin1Char('\'')) {
5374                 close = c;
5375             }
5376         } else if (c == close) {
5377             if (subst)
5378                 subst = false;
5379             else
5380                 close = QChar();
5381         }
5382     }
5383 
5384     cmd->cmd = line->mid(0, i).trimmed();
5385 
5386     // command arguments starts with first non-letter character
5387     cmd->args = cmd->cmd.section(QRegExp(_("(?=[^a-zA-Z])")), 1);
5388     if (!cmd->args.isEmpty()) {
5389         cmd->cmd.chop(cmd->args.size());
5390         cmd->args = cmd->args.trimmed();
5391 
5392         // '!' at the end of command
5393         cmd->hasBang = cmd->args.startsWith(QLatin1Char('!'));
5394         if (cmd->hasBang)
5395             cmd->args = cmd->args.mid(1).trimmed();
5396     }
5397 
5398     // remove the first command from command line
5399     line->remove(0, i + 1);
5400 
5401     return true;
5402 }
5403 
parseLineRange(QString * line,ExCommand * cmd)5404 bool FakeVimHandler::Private::parseLineRange(QString *line, ExCommand *cmd)
5405 {
5406     // remove leading colons and spaces
5407     line->remove(QRegExp(_("^\\s*(:+\\s*)*")));
5408 
5409     // special case ':!...' (use invalid range)
5410     if (line->startsWith(QLatin1Char('!'))) {
5411         cmd->range = Range();
5412         return true;
5413     }
5414 
5415     // FIXME: that seems to be different for %w and %s
5416     if (line->startsWith(QLatin1Char('%')))
5417         line->replace(0, 1, _("1,$"));
5418 
5419     int beginLine = parseLineAddress(line);
5420     int endLine;
5421     if (line->startsWith(QLatin1Char(','))) {
5422         *line = line->mid(1).trimmed();
5423         endLine = parseLineAddress(line);
5424     } else {
5425         endLine = beginLine;
5426     }
5427     if (beginLine == -1 || endLine == -1)
5428         return false;
5429 
5430     const int beginPos = firstPositionInLine(qMin(beginLine, endLine) + 1, false);
5431     const int endPos = lastPositionInLine(qMax(beginLine, endLine) + 1, false);
5432     cmd->range = Range(beginPos, endPos, RangeLineMode);
5433     cmd->count = beginLine;
5434 
5435     return true;
5436 }
5437 
parseRangeCount(const QString & line,Range * range) const5438 void FakeVimHandler::Private::parseRangeCount(const QString &line, Range *range) const
5439 {
5440     bool ok;
5441     const int count = qAbs(line.trimmed().toInt(&ok));
5442     if (ok) {
5443         const int beginLine = document()->findBlock(range->endPos).blockNumber() + 1;
5444         const int endLine = qMin(beginLine + count - 1, document()->blockCount());
5445         range->beginPos = firstPositionInLine(beginLine, false);
5446         range->endPos = lastPositionInLine(endLine, false);
5447     }
5448 }
5449 
5450 // use handleExCommand for invoking commands that might move the cursor
handleCommand(const QString & cmd)5451 void FakeVimHandler::Private::handleCommand(const QString &cmd)
5452 {
5453     handleExCommand(cmd);
5454 }
5455 
handleExSubstituteCommand(const ExCommand & cmd)5456 bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
5457 {
5458     // :substitute
5459     if (!cmd.matches(_("s"), _("substitute"))
5460         && !(cmd.cmd.isEmpty() && !cmd.args.isEmpty() && QString::fromLatin1("&~").contains(cmd.args[0]))) {
5461         return false;
5462     }
5463 
5464     int count = 1;
5465     QString line = cmd.args;
5466     const int countIndex = line.lastIndexOf(QRegExp(_("\\d+$")));
5467     if (countIndex != -1) {
5468         count = line.mid(countIndex).toInt();
5469         line = line.mid(0, countIndex).trimmed();
5470     }
5471 
5472     if (cmd.cmd.isEmpty()) {
5473         // keep previous substitution flags on '&&' and '~&'
5474         if (line.size() > 1 && line[1] == QLatin1Char('&'))
5475             g.lastSubstituteFlags += line.mid(2);
5476         else
5477             g.lastSubstituteFlags = line.mid(1);
5478         if (line[0] == QLatin1Char('~'))
5479             g.lastSubstitutePattern = g.lastSearch;
5480     } else {
5481         if (line.isEmpty()) {
5482             g.lastSubstituteFlags.clear();
5483         } else {
5484             // we have /{pattern}/{string}/[flags]  now
5485             const QChar separator = line.at(0);
5486             int pos1 = findUnescaped(separator, line, 1);
5487             if (pos1 == -1)
5488                 return false;
5489             int pos2 = findUnescaped(separator, line, pos1 + 1);
5490             if (pos2 == -1)
5491                 pos2 = line.size();
5492 
5493             g.lastSubstitutePattern = line.mid(1, pos1 - 1);
5494             g.lastSubstituteReplacement = line.mid(pos1 + 1, pos2 - pos1 - 1);
5495             g.lastSubstituteFlags = line.mid(pos2 + 1);
5496         }
5497     }
5498 
5499     count = qMax(1, count);
5500     QString needle = g.lastSubstitutePattern;
5501 
5502     if (g.lastSubstituteFlags.contains(QLatin1Char('i')))
5503         needle.prepend(_("\\c"));
5504 
5505     QRegExp pattern = vimPatternToQtPattern(needle, hasConfig(ConfigIgnoreCase),
5506                                             hasConfig(ConfigSmartCase));
5507 
5508     QTextBlock lastBlock;
5509     QTextBlock firstBlock;
5510     const bool global = g.lastSubstituteFlags.contains(QLatin1Char('g'));
5511     for (int a = 0; a != count; ++a) {
5512         for (QTextBlock block = document()->findBlock(cmd.range.endPos);
5513             block.isValid() && block.position() + block.length() > cmd.range.beginPos;
5514             block = block.previous()) {
5515             QString text = block.text();
5516             if (substituteText(&text, pattern, g.lastSubstituteReplacement, global)) {
5517                 firstBlock = block;
5518                 if (!lastBlock.isValid()) {
5519                     lastBlock = block;
5520                     beginEditBlock();
5521                 }
5522                 QTextCursor tc = m_cursor;
5523                 const int pos = block.position();
5524                 const int anchor = pos + block.length() - 1;
5525                 tc.setPosition(anchor);
5526                 tc.setPosition(pos, KeepAnchor);
5527                 tc.insertText(text);
5528             }
5529         }
5530     }
5531 
5532     if (lastBlock.isValid()) {
5533         m_buffer->undoState.position = CursorPosition(firstBlock.blockNumber(), 0);
5534 
5535         leaveVisualMode();
5536         setPosition(lastBlock.position());
5537         setAnchor();
5538         moveToFirstNonBlankOnLine();
5539         setTargetColumn();
5540 
5541         endEditBlock();
5542     }
5543 
5544     return true;
5545 }
5546 
handleExMapCommand(const ExCommand & cmd0)5547 bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map
5548 {
5549     QByteArray modes;
5550     enum Type { Map, Noremap, Unmap } type;
5551 
5552     QByteArray cmd = cmd0.cmd.toLatin1();
5553 
5554     // Strange formatting. But everything else is even uglier.
5555     if (cmd == "map") { modes = "nvo"; type = Map; } else
5556     if (cmd == "nm" || cmd == "nmap") { modes = "n"; type = Map; } else
5557     if (cmd == "vm" || cmd == "vmap") { modes = "v"; type = Map; } else
5558     if (cmd == "xm" || cmd == "xmap") { modes = "x"; type = Map; } else
5559     if (cmd == "smap") { modes = "s"; type = Map; } else
5560     if (cmd == "omap") { modes = "o"; type = Map; } else
5561     if (cmd == "map!") { modes = "ic"; type = Map; } else
5562     if (cmd == "im" || cmd == "imap") { modes = "i"; type = Map; } else
5563     if (cmd == "lm" || cmd == "lmap") { modes = "l"; type = Map; } else
5564     if (cmd == "cm" || cmd == "cmap") { modes = "c"; type = Map; } else
5565 
5566     if (cmd == "no" || cmd == "noremap") { modes = "nvo"; type = Noremap; } else
5567     if (cmd == "nn" || cmd == "nnoremap") { modes = "n"; type = Noremap; } else
5568     if (cmd == "vn" || cmd == "vnoremap") { modes = "v"; type = Noremap; } else
5569     if (cmd == "xn" || cmd == "xnoremap") { modes = "x"; type = Noremap; } else
5570     if (cmd == "snor" || cmd == "snoremap") { modes = "s"; type = Noremap; } else
5571     if (cmd == "ono" || cmd == "onoremap") { modes = "o"; type = Noremap; } else
5572     if (cmd == "no!" || cmd == "noremap!") { modes = "ic"; type = Noremap; } else
5573     if (cmd == "ino" || cmd == "inoremap") { modes = "i"; type = Noremap; } else
5574     if (cmd == "ln" || cmd == "lnoremap") { modes = "l"; type = Noremap; } else
5575     if (cmd == "cno" || cmd == "cnoremap") { modes = "c"; type = Noremap; } else
5576 
5577     if (cmd == "unm" || cmd == "unmap") { modes = "nvo"; type = Unmap; } else
5578     if (cmd == "nun" || cmd == "nunmap") { modes = "n"; type = Unmap; } else
5579     if (cmd == "vu" || cmd == "vunmap") { modes = "v"; type = Unmap; } else
5580     if (cmd == "xu" || cmd == "xunmap") { modes = "x"; type = Unmap; } else
5581     if (cmd == "sunm" || cmd == "sunmap") { modes = "s"; type = Unmap; } else
5582     if (cmd == "ou" || cmd == "ounmap") { modes = "o"; type = Unmap; } else
5583     if (cmd == "unm!" || cmd == "unmap!") { modes = "ic"; type = Unmap; } else
5584     if (cmd == "iu" || cmd == "iunmap") { modes = "i"; type = Unmap; } else
5585     if (cmd == "lu" || cmd == "lunmap") { modes = "l"; type = Unmap; } else
5586     if (cmd == "cu" || cmd == "cunmap") { modes = "c"; type = Unmap; }
5587 
5588     else
5589         return false;
5590 
5591     QString args = cmd0.args;
5592     bool silent = false;
5593     bool unique = false;
5594     forever {
5595         if (eatString("<silent>", &args)) {
5596             silent = true;
5597         } else if (eatString("<unique>", &args)) {
5598             continue;
5599         } else if (eatString("<special>", &args)) {
5600             continue;
5601         } else if (eatString("<buffer>", &args)) {
5602             notImplementedYet();
5603             continue;
5604         } else if (eatString("<script>", &args)) {
5605             notImplementedYet();
5606             continue;
5607         } else if (eatString("<expr>", &args)) {
5608             notImplementedYet();
5609             return true;
5610         }
5611         break;
5612     }
5613 
5614     const QString lhs = args.section(QRegExp(_("\\s+")), 0, 0);
5615     const QString rhs = args.section(QRegExp(_("\\s+")), 1);
5616     if ((rhs.isNull() && type != Unmap) || (!rhs.isNull() && type == Unmap)) {
5617         // FIXME: Dump mappings here.
5618         //qDebug() << g.mappings;
5619         return true;
5620     }
5621 
5622     Inputs key(lhs);
5623     //qDebug() << "MAPPING: " << modes << lhs << rhs;
5624     switch (type) {
5625         case Unmap:
5626             foreach (char c, modes)
5627                 MappingsIterator(&g.mappings, c, key).remove();
5628             break;
5629         case Map: // fall through
5630         case Noremap: {
5631             Inputs inputs(rhs, type == Noremap, silent);
5632             foreach (char c, modes)
5633                 MappingsIterator(&g.mappings, c).setInputs(key, inputs, unique);
5634             break;
5635         }
5636     }
5637     return true;
5638 }
5639 
handleExHistoryCommand(const ExCommand & cmd)5640 bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd)
5641 {
5642     // :his[tory]
5643     if (!cmd.matches(_("his"), _("history")))
5644         return false;
5645 
5646     if (cmd.args.isEmpty()) {
5647         QString info;
5648         info += _("#  command history\n");
5649         int i = 0;
5650         foreach (const QString &item, g.commandBuffer.historyItems()) {
5651             ++i;
5652             info += QString::fromLatin1("%1 %2\n").arg(i, -8).arg(item);
5653         }
5654         emit q->extraInformationChanged(info);
5655     } else {
5656         notImplementedYet();
5657     }
5658     updateMiniBuffer();
5659     return true;
5660 }
5661 
handleExRegisterCommand(const ExCommand & cmd)5662 bool FakeVimHandler::Private::handleExRegisterCommand(const ExCommand &cmd)
5663 {
5664     // :reg[isters] and :di[splay]
5665     if (!cmd.matches(_("reg"), _("registers")) && !cmd.matches(_("di"), _("display")))
5666         return false;
5667 
5668     QByteArray regs = cmd.args.toLatin1();
5669     if (regs.isEmpty()) {
5670         regs = "\"0123456789";
5671         QHashIterator<int, Register> it(g.registers);
5672         while (it.hasNext()) {
5673             it.next();
5674             if (it.key() > '9')
5675                 regs += char(it.key());
5676         }
5677     }
5678     QString info;
5679     info += _("--- Registers ---\n");
5680     foreach (char reg, regs) {
5681         QString value = quoteUnprintable(registerContents(reg));
5682         info += QString::fromLatin1("\"%1   %2\n").arg(reg).arg(value);
5683     }
5684     emit q->extraInformationChanged(info);
5685     updateMiniBuffer();
5686     return true;
5687 }
5688 
handleExSetCommand(const ExCommand & cmd)5689 bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd)
5690 {
5691     // :se[t]
5692     if (!cmd.matches(_("se"), _("set")))
5693         return false;
5694 
5695     clearMessage();
5696     QTC_CHECK(!cmd.args.isEmpty()); // Handled by plugin.
5697 
5698     if (cmd.args.contains(QLatin1Char('='))) {
5699         // Non-boolean config to set.
5700         int p = cmd.args.indexOf(QLatin1Char('='));
5701         QString error = theFakeVimSettings()
5702                 ->trySetValue(cmd.args.left(p), cmd.args.mid(p + 1));
5703         if (!error.isEmpty())
5704             showMessage(MessageError, error);
5705     } else {
5706         QString optionName = cmd.args;
5707 
5708         bool toggleOption = optionName.endsWith(QLatin1Char('!'));
5709         bool printOption = !toggleOption && optionName.endsWith(QLatin1Char('?'));
5710         if (printOption || toggleOption)
5711             optionName.chop(1);
5712 
5713         bool negateOption = optionName.startsWith(_("no"));
5714         if (negateOption)
5715             optionName.remove(0, 2);
5716 
5717         SavedAction *act = theFakeVimSettings()->item(optionName);
5718         if (!act) {
5719             showMessage(MessageError, FakeVimHandler::tr("Unknown option:")
5720                         + QLatin1Char(' ') + cmd.args);
5721         } else if (act->defaultValue().type() == QVariant::Bool) {
5722             bool oldValue = act->value().toBool();
5723             if (printOption) {
5724                 showMessage(MessageInfo, (oldValue ? _("") : _("no"))
5725                             + act->settingsKey().toLower());
5726             } else if (toggleOption || negateOption == oldValue) {
5727                 act->setValue(!oldValue);
5728             }
5729         } else if (negateOption && !printOption) {
5730             showMessage(MessageError, FakeVimHandler::tr("Invalid argument:")
5731                         + QLatin1Char(' ') + cmd.args);
5732         } else if (toggleOption) {
5733             showMessage(MessageError, FakeVimHandler::tr("Trailing characters:")
5734                         + QLatin1Char(' ') + cmd.args);
5735         } else {
5736             showMessage(MessageInfo, act->settingsKey().toLower() + _("=")
5737                         + act->value().toString());
5738         }
5739     }
5740     updateEditor();
5741     updateHighlights();
5742     return true;
5743 }
5744 
handleExNormalCommand(const ExCommand & cmd)5745 bool FakeVimHandler::Private::handleExNormalCommand(const ExCommand &cmd)
5746 {
5747     // :norm[al]
5748     if (!cmd.matches(_("norm"), _("normal")))
5749         return false;
5750     //qDebug() << "REPLAY NORMAL: " << quoteUnprintable(reNormal.cap(3));
5751     replay(cmd.args);
5752     return true;
5753 }
5754 
handleExYankDeleteCommand(const ExCommand & cmd)5755 bool FakeVimHandler::Private::handleExYankDeleteCommand(const ExCommand &cmd)
5756 {
5757     // :[range]d[elete] [x] [count]
5758     // :[range]y[ank] [x] [count]
5759     const bool remove = cmd.matches(_("d"), _("delete"));
5760     if (!remove && !cmd.matches(_("y"), _("yank")))
5761         return false;
5762 
5763     // get register from arguments
5764     const bool hasRegisterArg = !cmd.args.isEmpty() && !cmd.args.at(0).isDigit();
5765     const int r = hasRegisterArg ? cmd.args.at(0).unicode() : m_register;
5766 
5767     // get [count] from arguments
5768     Range range = cmd.range;
5769     parseRangeCount(cmd.args.mid(hasRegisterArg ? 1 : 0).trimmed(), &range);
5770 
5771     yankText(range, r);
5772 
5773     if (remove) {
5774         leaveVisualMode();
5775         setPosition(range.beginPos);
5776         pushUndoState();
5777         setCurrentRange(range);
5778         removeText(currentRange());
5779     }
5780 
5781     return true;
5782 }
5783 
handleExChangeCommand(const ExCommand & cmd)5784 bool FakeVimHandler::Private::handleExChangeCommand(const ExCommand &cmd)
5785 {
5786     // :[range]c[hange]
5787     if (!cmd.matches(_("c"), _("change")))
5788         return false;
5789 
5790     Range range = cmd.range;
5791     range.rangemode = RangeLineModeExclusive;
5792     removeText(range);
5793     insertAutomaticIndentation(true, cmd.hasBang);
5794 
5795     // FIXME: In Vim same or less number of lines can be inserted and position after insertion is
5796     //        beginning of last inserted line.
5797     enterInsertMode();
5798 
5799     return true;
5800 }
5801 
handleExMoveCommand(const ExCommand & cmd)5802 bool FakeVimHandler::Private::handleExMoveCommand(const ExCommand &cmd)
5803 {
5804     // :[range]m[ove] {address}
5805     if (!cmd.matches(_("m"), _("move")))
5806         return false;
5807 
5808     QString lineCode = cmd.args;
5809 
5810     const int startLine = document()->findBlock(cmd.range.beginPos).blockNumber();
5811     const int endLine = document()->findBlock(cmd.range.endPos).blockNumber();
5812     const int lines = endLine - startLine + 1;
5813 
5814     int targetLine = lineCode == _("0") ? -1 : parseLineAddress(&lineCode);
5815     if (targetLine >= startLine && targetLine < endLine) {
5816         showMessage(MessageError, FakeVimHandler::tr("Move lines into themselves."));
5817         return true;
5818     }
5819 
5820     CursorPosition lastAnchor = markLessPosition();
5821     CursorPosition lastPosition = markGreaterPosition();
5822 
5823     recordJump();
5824     setPosition(cmd.range.beginPos);
5825     pushUndoState();
5826 
5827     setCurrentRange(cmd.range);
5828     QString text = selectText(cmd.range);
5829     removeText(currentRange());
5830 
5831     const bool insertAtEnd = targetLine == document()->blockCount();
5832     if (targetLine >= startLine)
5833         targetLine -= lines;
5834     QTextBlock block = document()->findBlockByNumber(insertAtEnd ? targetLine : targetLine + 1);
5835     setPosition(block.position());
5836     setAnchor();
5837 
5838     if (insertAtEnd) {
5839         moveBehindEndOfLine();
5840         text.chop(1);
5841         insertText(QString::fromLatin1("\n"));
5842     }
5843     insertText(text);
5844 
5845     if (!insertAtEnd)
5846         moveUp(1);
5847     if (hasConfig(ConfigStartOfLine))
5848         moveToFirstNonBlankOnLine();
5849 
5850     // correct last selection
5851     leaveVisualMode();
5852     if (lastAnchor.line >= startLine && lastAnchor.line <= endLine)
5853         lastAnchor.line += targetLine - startLine + 1;
5854     if (lastPosition.line >= startLine && lastPosition.line <= endLine)
5855         lastPosition.line += targetLine - startLine + 1;
5856     setMark(QLatin1Char('<'), lastAnchor);
5857     setMark(QLatin1Char('>'), lastPosition);
5858 
5859     if (lines > 2)
5860         showMessage(MessageInfo, FakeVimHandler::tr("%n lines moved.", 0, lines));
5861 
5862     return true;
5863 }
5864 
handleExJoinCommand(const ExCommand & cmd)5865 bool FakeVimHandler::Private::handleExJoinCommand(const ExCommand &cmd)
5866 {
5867     // :[range]j[oin][!] [count]
5868     // FIXME: Argument [count] can follow immediately.
5869     if (!cmd.matches(_("j"), _("join")))
5870         return false;
5871 
5872     // get [count] from arguments
5873     bool ok;
5874     int count = cmd.args.toInt(&ok);
5875 
5876     if (ok) {
5877         setPosition(cmd.range.endPos);
5878     } else {
5879         setPosition(cmd.range.beginPos);
5880         const int startLine = document()->findBlock(cmd.range.beginPos).blockNumber();
5881         const int endLine = document()->findBlock(cmd.range.endPos).blockNumber();
5882         count = endLine - startLine + 1;
5883     }
5884 
5885     moveToStartOfLine();
5886     pushUndoState();
5887     joinLines(count, cmd.hasBang);
5888 
5889     moveToFirstNonBlankOnLine();
5890 
5891     return true;
5892 }
5893 
handleExWriteCommand(const ExCommand & cmd)5894 bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd)
5895 {
5896     // :w, :x, :wq, ...
5897     //static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
5898     if (cmd.cmd != _("w") && cmd.cmd != _("x") && cmd.cmd != _("wq"))
5899         return false;
5900 
5901     int beginLine = lineForPosition(cmd.range.beginPos);
5902     int endLine = lineForPosition(cmd.range.endPos);
5903     const bool noArgs = (beginLine == -1);
5904     if (beginLine == -1)
5905         beginLine = 0;
5906     if (endLine == -1)
5907         endLine = linesInDocument();
5908     //qDebug() << "LINES: " << beginLine << endLine;
5909     //QString prefix = cmd.args;
5910     const bool forced = cmd.hasBang;
5911     //const bool quit = prefix.contains(QLatin1Char('q')) || prefix.contains(QLatin1Char('x'));
5912     //const bool quitAll = quit && prefix.contains(QLatin1Char('a'));
5913     QString fileName = cmd.args;
5914     if (fileName.isEmpty())
5915         fileName = m_currentFileName;
5916     QFile file1(fileName);
5917     const bool exists = file1.exists();
5918     if (exists && !forced && !noArgs) {
5919         showMessage(MessageError, FakeVimHandler::tr
5920             ("File \"%1\" exists (add ! to override)").arg(fileName));
5921     } else if (file1.open(QIODevice::ReadWrite)) {
5922         // Nobody cared, so act ourselves.
5923         file1.close();
5924         Range range(firstPositionInLine(beginLine),
5925             firstPositionInLine(endLine), RangeLineMode);
5926         QString contents = selectText(range);
5927         QFile::remove(fileName);
5928         QFile file2(fileName);
5929         if (file2.open(QIODevice::ReadWrite)) {
5930             QTextStream ts(&file2);
5931             ts << contents;
5932         } else {
5933             showMessage(MessageError, FakeVimHandler::tr
5934                ("Cannot open file \"%1\" for writing").arg(fileName));
5935         }
5936         // Check result by reading back.
5937         QFile file3(fileName);
5938         file3.open(QIODevice::ReadOnly);
5939         QByteArray ba = file3.readAll();
5940         showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2 %3L, %4C written.")
5941             .arg(fileName).arg(exists ? _(" ") : tr(" [New] "))
5942             .arg(ba.count('\n')).arg(ba.size()));
5943         //if (quitAll)
5944         //    passUnknownExCommand(forced ? "qa!" : "qa");
5945         //else if (quit)
5946         //    passUnknownExCommand(forced ? "q!" : "q");
5947     } else {
5948         showMessage(MessageError, FakeVimHandler::tr
5949             ("Cannot open file \"%1\" for reading").arg(fileName));
5950     }
5951     return true;
5952 }
5953 
handleExReadCommand(const ExCommand & cmd)5954 bool FakeVimHandler::Private::handleExReadCommand(const ExCommand &cmd)
5955 {
5956     // :r[ead]
5957     if (!cmd.matches(_("r"), _("read")))
5958         return false;
5959 
5960     beginEditBlock();
5961 
5962     moveToStartOfLine();
5963     setTargetColumn();
5964     moveDown();
5965     int pos = position();
5966 
5967     m_currentFileName = cmd.args;
5968     QFile file(m_currentFileName);
5969     file.open(QIODevice::ReadOnly);
5970     QTextStream ts(&file);
5971     QString data = ts.readAll();
5972     insertText(data);
5973 
5974     setAnchorAndPosition(pos, pos);
5975 
5976     endEditBlock();
5977 
5978     showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2L, %3C")
5979         .arg(m_currentFileName).arg(data.count(QLatin1Char('\n'))).arg(data.size()));
5980 
5981     return true;
5982 }
5983 
handleExBangCommand(const ExCommand & cmd)5984 bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :!
5985 {
5986     if (!cmd.cmd.isEmpty() || !cmd.hasBang)
5987         return false;
5988 
5989     bool replaceText = cmd.range.isValid();
5990     const QString command = QString(cmd.cmd.mid(1) + QLatin1Char(' ') + cmd.args).trimmed();
5991     const QString input = replaceText ? selectText(cmd.range) : QString();
5992 
5993     const QString result = getProcessOutput(command, input);
5994 
5995     if (replaceText) {
5996         setCurrentRange(cmd.range);
5997         int targetPosition = firstPositionInLine(lineForPosition(cmd.range.beginPos));
5998         beginEditBlock();
5999         removeText(currentRange());
6000         insertText(result);
6001         setPosition(targetPosition);
6002         endEditBlock();
6003         leaveVisualMode();
6004         //qDebug() << "FILTER: " << command;
6005         showMessage(MessageInfo, FakeVimHandler::tr("%n lines filtered.", 0,
6006             input.count(QLatin1Char('\n'))));
6007     } else if (!result.isEmpty()) {
6008         emit q->extraInformationChanged(result);
6009     }
6010 
6011     return true;
6012 }
6013 
handleExShiftCommand(const ExCommand & cmd)6014 bool FakeVimHandler::Private::handleExShiftCommand(const ExCommand &cmd)
6015 {
6016     // :[range]{<|>}* [count]
6017     if (!cmd.cmd.isEmpty() || (!cmd.args.startsWith(QLatin1Char('<')) && !cmd.args.startsWith(QLatin1Char('>'))))
6018         return false;
6019 
6020     const QChar c = cmd.args.at(0);
6021 
6022     // get number of repetition
6023     int repeat = 1;
6024     int i = 1;
6025     for (; i < cmd.args.size(); ++i) {
6026         const QChar c2 = cmd.args.at(i);
6027         if (c2 == c)
6028             ++repeat;
6029         else if (!c2.isSpace())
6030             break;
6031     }
6032 
6033     // get [count] from arguments
6034     Range range = cmd.range;
6035     parseRangeCount(cmd.args.mid(i), &range);
6036 
6037     setCurrentRange(range);
6038     if (c == QLatin1Char('<'))
6039         shiftRegionLeft(repeat);
6040     else
6041         shiftRegionRight(repeat);
6042 
6043     leaveVisualMode();
6044 
6045     return true;
6046 }
6047 
handleExNohlsearchCommand(const ExCommand & cmd)6048 bool FakeVimHandler::Private::handleExNohlsearchCommand(const ExCommand &cmd)
6049 {
6050     // :noh, :nohl, ..., :nohlsearch
6051     if (cmd.cmd.size() < 3 || !QString(_("nohlsearch")).startsWith(cmd.cmd))
6052         return false;
6053 
6054     g.highlightsCleared = true;
6055     updateHighlights();
6056     return true;
6057 }
6058 
handleExUndoRedoCommand(const ExCommand & cmd)6059 bool FakeVimHandler::Private::handleExUndoRedoCommand(const ExCommand &cmd)
6060 {
6061     // :undo
6062     // :redo
6063     bool undo = (cmd.cmd == _("u") || cmd.cmd == _("un") || cmd.cmd == _("undo"));
6064     if (!undo && cmd.cmd != _("red") && cmd.cmd != _("redo"))
6065         return false;
6066 
6067     undoRedo(undo);
6068     updateMiniBuffer();
6069     return true;
6070 }
6071 
handleExGotoCommand(const ExCommand & cmd)6072 bool FakeVimHandler::Private::handleExGotoCommand(const ExCommand &cmd)
6073 {
6074     // :{address}
6075     if (!cmd.cmd.isEmpty() || !cmd.args.isEmpty())
6076         return false;
6077 
6078     const int beginLine = lineForPosition(cmd.range.endPos);
6079     setPosition(firstPositionInLine(beginLine));
6080     clearMessage();
6081     return true;
6082 }
6083 
handleExSourceCommand(const ExCommand & cmd)6084 bool FakeVimHandler::Private::handleExSourceCommand(const ExCommand &cmd)
6085 {
6086     // :source
6087     if (cmd.cmd != _("so") && cmd.cmd != _("source"))
6088         return false;
6089 
6090     QString fileName = cmd.args;
6091     QFile file(fileName);
6092     if (!file.open(QIODevice::ReadOnly)) {
6093         showMessage(MessageError, FakeVimHandler::tr("Cannot open file %1").arg(fileName));
6094         return true;
6095     }
6096 
6097     bool inFunction = false;
6098     QByteArray line;
6099     while (!file.atEnd() || !line.isEmpty()) {
6100         QByteArray nextline = !file.atEnd() ? file.readLine() : QByteArray();
6101 
6102         //  remove comment
6103         int i = nextline.lastIndexOf('"');
6104         if (i != -1)
6105             nextline = nextline.remove(i, nextline.size() - i);
6106 
6107         nextline = nextline.trimmed();
6108 
6109         // multi-line command?
6110         if (nextline.startsWith('\\')) {
6111             line += nextline.mid(1);
6112             continue;
6113         }
6114 
6115         if (line.startsWith("function")) {
6116             //qDebug() << "IGNORING FUNCTION" << line;
6117             inFunction = true;
6118         } else if (inFunction && line.startsWith("endfunction")) {
6119             inFunction = false;
6120         } else if (!line.isEmpty() && !inFunction) {
6121             //qDebug() << "EXECUTING: " << line;
6122             ExCommand cmd;
6123             QString commandLine = QString::fromLocal8Bit(line);
6124             while (parseExCommmand(&commandLine, &cmd)) {
6125                 if (!handleExCommandHelper(cmd))
6126                     break;
6127             }
6128         }
6129 
6130         line = nextline;
6131     }
6132     file.close();
6133     return true;
6134 }
6135 
handleExEchoCommand(const ExCommand & cmd)6136 bool FakeVimHandler::Private::handleExEchoCommand(const ExCommand &cmd)
6137 {
6138     // :echo
6139     if (cmd.cmd != _("echo"))
6140         return false;
6141     showMessage(MessageInfo, cmd.args);
6142     return true;
6143 }
6144 
handleExCommand(const QString & line0)6145 void FakeVimHandler::Private::handleExCommand(const QString &line0)
6146 {
6147     QString line = line0; // Make sure we have a copy to prevent aliasing.
6148 
6149     if (line.endsWith(QLatin1Char('%'))) {
6150         line.chop(1);
6151         int percent = line.toInt();
6152         setPosition(firstPositionInLine(percent * linesInDocument() / 100));
6153         clearMessage();
6154         return;
6155     }
6156 
6157     //qDebug() << "CMD: " << cmd;
6158 
6159     enterCommandMode(g.returnToMode);
6160 
6161     beginLargeEditBlock();
6162     ExCommand cmd;
6163     QString lastCommand = line;
6164     while (parseExCommmand(&line, &cmd)) {
6165         if (!handleExCommandHelper(cmd)) {
6166             showMessage(MessageError, tr("Not an editor command: %1").arg(lastCommand));
6167             break;
6168         }
6169         lastCommand = line;
6170     }
6171 
6172     // if the last command closed the editor, we would crash here (:vs and then :on)
6173     if (!(m_textedit || m_plaintextedit))
6174         return;
6175 
6176     endEditBlock();
6177 
6178     resetCommandMode();
6179 }
6180 
handleExCommandHelper(ExCommand & cmd)6181 bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
6182 {
6183     return handleExPluginCommand(cmd)
6184         || handleExGotoCommand(cmd)
6185         || handleExBangCommand(cmd)
6186         || handleExHistoryCommand(cmd)
6187         || handleExRegisterCommand(cmd)
6188         || handleExYankDeleteCommand(cmd)
6189         || handleExChangeCommand(cmd)
6190         || handleExMoveCommand(cmd)
6191         || handleExJoinCommand(cmd)
6192         || handleExMapCommand(cmd)
6193         || handleExNohlsearchCommand(cmd)
6194         || handleExNormalCommand(cmd)
6195         || handleExReadCommand(cmd)
6196         || handleExUndoRedoCommand(cmd)
6197         || handleExSetCommand(cmd)
6198         || handleExShiftCommand(cmd)
6199         || handleExSourceCommand(cmd)
6200         || handleExSubstituteCommand(cmd)
6201         || handleExWriteCommand(cmd)
6202         || handleExEchoCommand(cmd);
6203 }
6204 
handleExPluginCommand(const ExCommand & cmd)6205 bool FakeVimHandler::Private::handleExPluginCommand(const ExCommand &cmd)
6206 {
6207     bool handled = false;
6208     int pos = m_cursor.position();
6209     commitCursor();
6210     emit q->handleExCommandRequested(&handled, cmd);
6211     //qDebug() << "HANDLER REQUEST: " << cmd.cmd << handled;
6212     if (handled && (m_textedit || m_plaintextedit)) {
6213         pullCursor();
6214         if (m_cursor.position() != pos)
6215             recordJump(pos);
6216     }
6217     return handled;
6218 }
6219 
searchBalanced(bool forward,QChar needle,QChar other)6220 void FakeVimHandler::Private::searchBalanced(bool forward, QChar needle, QChar other)
6221 {
6222     int level = 1;
6223     int pos = position();
6224     const int npos = forward ? lastPositionInDocument() : 0;
6225     while (true) {
6226         if (forward)
6227             ++pos;
6228         else
6229             --pos;
6230         if (pos == npos)
6231             return;
6232         QChar c = document()->characterAt(pos);
6233         if (c == other)
6234             ++level;
6235         else if (c == needle)
6236             --level;
6237         if (level == 0) {
6238             const int oldLine = cursorLine() - cursorLineOnScreen();
6239             // Making this unconditional feels better, but is not "vim like".
6240             if (oldLine != cursorLine() - cursorLineOnScreen())
6241                 scrollToLine(cursorLine() - linesOnScreen() / 2);
6242             recordJump();
6243             setPosition(pos);
6244             setTargetColumn();
6245             return;
6246         }
6247     }
6248 }
6249 
search(const SearchData & sd,int startPos,int count,bool showMessages)6250 QTextCursor FakeVimHandler::Private::search(const SearchData &sd, int startPos, int count,
6251     bool showMessages)
6252 {
6253     QRegExp needleExp = vimPatternToQtPattern(sd.needle, hasConfig(ConfigIgnoreCase),
6254                                               hasConfig(ConfigSmartCase));
6255     if (!needleExp.isValid()) {
6256         if (showMessages) {
6257             QString error = needleExp.errorString();
6258             showMessage(MessageError,
6259                         FakeVimHandler::tr("Invalid regular expression: %1").arg(error));
6260         }
6261         if (sd.highlightMatches)
6262             highlightMatches(QString());
6263         return QTextCursor();
6264     }
6265 
6266     int repeat = count;
6267     const int pos = startPos + (sd.forward ? 1 : -1);
6268 
6269     QTextCursor tc;
6270     if (pos >= 0 && pos < document()->characterCount()) {
6271         tc = QTextCursor(document());
6272         tc.setPosition(pos);
6273         if (sd.forward && afterEndOfLine(document(), pos))
6274             tc.movePosition(Right);
6275 
6276         if (!tc.isNull()) {
6277             if (sd.forward)
6278                 searchForward(&tc, needleExp, &repeat);
6279             else
6280                 searchBackward(&tc, needleExp, &repeat);
6281         }
6282     }
6283 
6284     if (tc.isNull()) {
6285         if (hasConfig(ConfigWrapScan)) {
6286             tc = QTextCursor(document());
6287             tc.movePosition(sd.forward ? StartOfDocument : EndOfDocument);
6288             if (sd.forward)
6289                 searchForward(&tc, needleExp, &repeat);
6290             else
6291                 searchBackward(&tc, needleExp, &repeat);
6292             if (tc.isNull()) {
6293                 if (showMessages) {
6294                     showMessage(MessageError,
6295                         FakeVimHandler::tr("Pattern not found: %1").arg(sd.needle));
6296                 }
6297             } else if (showMessages) {
6298                 QString msg = sd.forward
6299                     ? FakeVimHandler::tr("Search hit BOTTOM, continuing at TOP.")
6300                     : FakeVimHandler::tr("Search hit TOP, continuing at BOTTOM.");
6301                 showMessage(MessageWarning, msg);
6302             }
6303         } else if (showMessages) {
6304             QString msg = sd.forward
6305                 ? FakeVimHandler::tr("Search hit BOTTOM without match for: %1")
6306                 : FakeVimHandler::tr("Search hit TOP without match for: %1");
6307             showMessage(MessageError, msg.arg(sd.needle));
6308         }
6309     }
6310 
6311     if (sd.highlightMatches)
6312         highlightMatches(needleExp.pattern());
6313 
6314     return tc;
6315 }
6316 
search(const SearchData & sd,bool showMessages)6317 void FakeVimHandler::Private::search(const SearchData &sd, bool showMessages)
6318 {
6319     const int oldLine = cursorLine() - cursorLineOnScreen();
6320 
6321     QTextCursor tc = search(sd, m_searchStartPosition, count(), showMessages);
6322     if (tc.isNull()) {
6323         tc = m_cursor;
6324         tc.setPosition(m_searchStartPosition);
6325     }
6326 
6327     if (isVisualMode()) {
6328         int d = tc.anchor() - tc.position();
6329         setPosition(tc.position() + d);
6330     } else {
6331         // Set Cursor. In contrast to the main editor we have the cursor
6332         // position before the anchor position.
6333         setAnchorAndPosition(tc.position(), tc.anchor());
6334     }
6335 
6336     // Making this unconditional feels better, but is not "vim like".
6337     if (oldLine != cursorLine() - cursorLineOnScreen())
6338         scrollToLine(cursorLine() - linesOnScreen() / 2);
6339 
6340     m_searchCursor = m_cursor;
6341 
6342     setTargetColumn();
6343 }
6344 
searchNext(bool forward)6345 bool FakeVimHandler::Private::searchNext(bool forward)
6346 {
6347     SearchData sd;
6348     sd.needle = g.lastSearch;
6349     sd.forward = forward ? g.lastSearchForward : !g.lastSearchForward;
6350     sd.highlightMatches = true;
6351     m_searchStartPosition = position();
6352     showMessage(MessageCommand, QLatin1Char(g.lastSearchForward ? '/' : '?') + sd.needle);
6353     recordJump();
6354     search(sd);
6355     return finishSearch();
6356 }
6357 
highlightMatches(const QString & needle)6358 void FakeVimHandler::Private::highlightMatches(const QString &needle)
6359 {
6360     g.lastNeedle = needle;
6361     g.highlightsCleared = false;
6362     updateHighlights();
6363 }
6364 
moveToFirstNonBlankOnLine()6365 void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
6366 {
6367     moveToFirstNonBlankOnLine(&m_cursor);
6368     setTargetColumn();
6369 }
6370 
moveToFirstNonBlankOnLine(QTextCursor * tc)6371 void FakeVimHandler::Private::moveToFirstNonBlankOnLine(QTextCursor *tc)
6372 {
6373     tc->setPosition(tc->block().position(), KeepAnchor);
6374     moveToNonBlankOnLine(tc);
6375 }
6376 
moveToFirstNonBlankOnLineVisually()6377 void FakeVimHandler::Private::moveToFirstNonBlankOnLineVisually()
6378 {
6379     moveToStartOfLineVisually();
6380     moveToNonBlankOnLine(&m_cursor);
6381     setTargetColumn();
6382 }
6383 
moveToNonBlankOnLine(QTextCursor * tc)6384 void FakeVimHandler::Private::moveToNonBlankOnLine(QTextCursor *tc)
6385 {
6386     QTextDocument *doc = tc->document();
6387     const QTextBlock block = tc->block();
6388     const int maxPos = block.position() + block.length() - 1;
6389     int i = tc->position();
6390     while (doc->characterAt(i).isSpace() && i < maxPos)
6391         ++i;
6392     tc->setPosition(i, KeepAnchor);
6393 }
6394 
indentSelectedText(QChar typedChar)6395 void FakeVimHandler::Private::indentSelectedText(QChar typedChar)
6396 {
6397     beginEditBlock();
6398     setTargetColumn();
6399     int beginLine = qMin(lineForPosition(position()), lineForPosition(anchor()));
6400     int endLine = qMax(lineForPosition(position()), lineForPosition(anchor()));
6401 
6402     Range range(anchor(), position(), g.rangemode);
6403     indentText(range, typedChar);
6404 
6405     setPosition(firstPositionInLine(beginLine));
6406     handleStartOfLine();
6407     setTargetColumn();
6408     setDotCommand(_("%1=="), endLine - beginLine + 1);
6409     endEditBlock();
6410 
6411     const int lines = endLine - beginLine + 1;
6412     if (lines > 2)
6413         showMessage(MessageInfo, FakeVimHandler::tr("%n lines indented.", 0, lines));
6414 }
6415 
indentText(const Range & range,QChar typedChar)6416 void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar)
6417 {
6418     int beginBlock = document()->findBlock(range.beginPos).blockNumber();
6419     int endBlock = document()->findBlock(range.endPos).blockNumber();
6420     if (beginBlock > endBlock)
6421         std::swap(beginBlock, endBlock);
6422 
6423     // Don't remember current indentation in last text insertion.
6424     const QString lastInsertion = m_buffer->lastInsertion;
6425     emit q->indentRegion(beginBlock, endBlock, typedChar);
6426     m_buffer->lastInsertion = lastInsertion;
6427 }
6428 
isElectricCharacter(QChar c) const6429 bool FakeVimHandler::Private::isElectricCharacter(QChar c) const
6430 {
6431     bool result = false;
6432     emit q->checkForElectricCharacter(&result, c);
6433     return result;
6434 }
6435 
shiftRegionRight(int repeat)6436 void FakeVimHandler::Private::shiftRegionRight(int repeat)
6437 {
6438     int beginLine = lineForPosition(anchor());
6439     int endLine = lineForPosition(position());
6440     int targetPos = anchor();
6441     if (beginLine > endLine) {
6442         std::swap(beginLine, endLine);
6443         targetPos = position();
6444     }
6445     if (hasConfig(ConfigStartOfLine))
6446         targetPos = firstPositionInLine(beginLine);
6447 
6448     const int sw = config(ConfigShiftWidth).toInt();
6449     g.movetype = MoveLineWise;
6450     beginEditBlock();
6451     QTextBlock block = document()->findBlockByLineNumber(beginLine - 1);
6452     while (block.isValid() && lineNumber(block) <= endLine) {
6453         const Column col = indentation(block.text());
6454         QTextCursor tc = m_cursor;
6455         tc.setPosition(block.position());
6456         if (col.physical > 0)
6457             tc.setPosition(tc.position() + col.physical, KeepAnchor);
6458         tc.insertText(tabExpand(col.logical + sw * repeat));
6459         block = block.next();
6460     }
6461     endEditBlock();
6462 
6463     setPosition(targetPos);
6464     handleStartOfLine();
6465     setTargetColumn();
6466 
6467     const int lines = endLine - beginLine + 1;
6468     if (lines > 2) {
6469         showMessage(MessageInfo,
6470             FakeVimHandler::tr("%n lines %1ed %2 time.", 0, lines)
6471             .arg(repeat > 0 ? '>' : '<').arg(qAbs(repeat)));
6472     }
6473 }
6474 
shiftRegionLeft(int repeat)6475 void FakeVimHandler::Private::shiftRegionLeft(int repeat)
6476 {
6477     shiftRegionRight(-repeat);
6478 }
6479 
moveToTargetColumn()6480 void FakeVimHandler::Private::moveToTargetColumn()
6481 {
6482     const QTextBlock &bl = block();
6483     //Column column = cursorColumn();
6484     //int logical = logical
6485     const int pos = lastPositionInLine(bl.blockNumber() + 1, false);
6486     if (m_targetColumn == -1) {
6487         setPosition(pos);
6488         return;
6489     }
6490     const int physical = bl.position() + logicalToPhysicalColumn(m_targetColumn, bl.text());
6491     //qDebug() << "CORRECTING COLUMN FROM: " << logical << "TO" << m_targetColumn;
6492     setPosition(qMin(pos, physical));
6493 }
6494 
setTargetColumn()6495 void FakeVimHandler::Private::setTargetColumn()
6496 {
6497     m_targetColumn = logicalCursorColumn();
6498     m_visualTargetColumn = m_targetColumn;
6499 
6500     QTextCursor tc = m_cursor;
6501     tc.movePosition(StartOfLine);
6502     m_targetColumnWrapped = m_cursor.position() - tc.position();
6503 }
6504 
6505 /* if simple is given:
6506  *  class 0: spaces
6507  *  class 1: non-spaces
6508  * else
6509  *  class 0: spaces
6510  *  class 1: non-space-or-letter-or-number
6511  *  class 2: letter-or-number
6512  */
6513 
6514 
charClass(QChar c,bool simple) const6515 int FakeVimHandler::Private::charClass(QChar c, bool simple) const
6516 {
6517     if (simple)
6518         return c.isSpace() ? 0 : 1;
6519     // FIXME: This means that only characters < 256 in the
6520     // ConfigIsKeyword setting are handled properly.
6521     if (c.unicode() < 256) {
6522         //int old = (c.isLetterOrNumber() || c.unicode() == QLatin1Char('_')) ? 2
6523         //    :  c.isSpace() ? 0 : 1;
6524         //qDebug() << c.unicode() << old << m_charClass[c.unicode()];
6525         return m_charClass[c.unicode()];
6526     }
6527     if (c.isLetterOrNumber() || c.unicode() == QLatin1Char('_'))
6528         return 2;
6529     return c.isSpace() ? 0 : 1;
6530 }
6531 
miniBufferTextEdited(const QString & text,int cursorPos,int anchorPos)6532 void FakeVimHandler::Private::miniBufferTextEdited(const QString &text, int cursorPos,
6533     int anchorPos)
6534 {
6535     if (g.subsubmode != SearchSubSubMode && g.mode != ExMode) {
6536         editor()->setFocus();
6537     } else if (text.isEmpty()) {
6538         // editing cancelled
6539         enterFakeVim();
6540         handleDefaultKey(Input(Qt::Key_Escape, Qt::NoModifier, QString()));
6541         leaveFakeVim();
6542 
6543         editor()->setFocus();
6544         updateCursorShape();
6545     } else {
6546         CommandBuffer &cmdBuf = (g.mode == ExMode) ? g.commandBuffer : g.searchBuffer;
6547         int pos = qMax(1, cursorPos);
6548         int anchor = anchorPos == -1 ? pos : qMax(1, anchorPos);
6549         QString buffer = text;
6550         // prepend prompt character if missing
6551         if (!buffer.startsWith(cmdBuf.prompt())) {
6552             buffer.prepend(cmdBuf.prompt());
6553             ++pos;
6554             ++anchor;
6555         }
6556         // update command/search buffer
6557         cmdBuf.setContents(buffer.mid(1), pos - 1, anchor - 1);
6558         if (pos != cursorPos || anchor != anchorPos || buffer != text)
6559             emit q->commandBufferChanged(buffer, pos, anchor, 0, q);
6560         // update search expression
6561         if (g.subsubmode == SearchSubSubMode) {
6562             updateFind(false);
6563             exportSelection();
6564         }
6565     }
6566 }
6567 
pullOrCreateBufferData()6568 void FakeVimHandler::Private::pullOrCreateBufferData()
6569 {
6570     const QVariant data = document()->property("FakeVimSharedData");
6571     if (data.isValid()) {
6572         // FakeVimHandler has been already created for this document (e.g. in other split).
6573         m_buffer = data.value<BufferDataPtr>();
6574     } else {
6575         // FakeVimHandler has not been created for this document yet.
6576         m_buffer = BufferDataPtr(new BufferData);
6577         document()->setProperty("FakeVimSharedData", QVariant::fromValue(m_buffer));
6578     }
6579 }
6580 
6581 // Helper to parse a-z,A-Z,48-57,_
someInt(const QString & str)6582 static int someInt(const QString &str)
6583 {
6584     if (str.toInt())
6585         return str.toInt();
6586     if (str.size())
6587         return str.at(0).unicode();
6588     return 0;
6589 }
6590 
setupCharClass()6591 void FakeVimHandler::Private::setupCharClass()
6592 {
6593     for (int i = 0; i < 256; ++i) {
6594         const QChar c = QChar(QLatin1Char(i));
6595         m_charClass[i] = c.isSpace() ? 0 : 1;
6596     }
6597     const QString conf = config(ConfigIsKeyword).toString();
6598     foreach (const QString &part, conf.split(QLatin1Char(','))) {
6599         if (part.contains(QLatin1Char('-'))) {
6600             const int from = someInt(part.section(QLatin1Char('-'), 0, 0));
6601             const int to = someInt(part.section(QLatin1Char('-'), 1, 1));
6602             for (int i = qMax(0, from); i <= qMin(255, to); ++i)
6603                 m_charClass[i] = 2;
6604         } else {
6605             m_charClass[qMin(255, someInt(part))] = 2;
6606         }
6607     }
6608 }
6609 
moveToBoundary(bool simple,bool forward)6610 void FakeVimHandler::Private::moveToBoundary(bool simple, bool forward)
6611 {
6612     QTextDocument *doc = document();
6613     QTextCursor tc(doc);
6614     tc.setPosition(position());
6615     if (forward ? tc.atBlockEnd() : tc.atBlockStart())
6616         return;
6617 
6618     QChar c = document()->characterAt(tc.position() + (forward ? -1 : 1));
6619     int lastClass = tc.atStart() ? -1 : charClass(c, simple);
6620     QTextCursor::MoveOperation op = forward ? Right : Left;
6621     while (true) {
6622         c = doc->characterAt(tc.position());
6623         int thisClass = charClass(c, simple);
6624         if (thisClass != lastClass || (forward ? tc.atBlockEnd() : tc.atBlockStart())) {
6625             if (tc != m_cursor)
6626                 tc.movePosition(forward ? Left : Right);
6627             break;
6628         }
6629         lastClass = thisClass;
6630         tc.movePosition(op);
6631     }
6632     setPosition(tc.position());
6633 }
6634 
moveToNextBoundary(bool end,int count,bool simple,bool forward)6635 void FakeVimHandler::Private::moveToNextBoundary(bool end, int count, bool simple, bool forward)
6636 {
6637     int repeat = count;
6638     while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
6639         setPosition(position() + (forward ? 1 : -1));
6640         moveToBoundary(simple, forward);
6641         if (atBoundary(end, simple))
6642             --repeat;
6643     }
6644 }
6645 
moveToNextBoundaryStart(int count,bool simple,bool forward)6646 void FakeVimHandler::Private::moveToNextBoundaryStart(int count, bool simple, bool forward)
6647 {
6648     moveToNextBoundary(false, count, simple, forward);
6649 }
6650 
moveToNextBoundaryEnd(int count,bool simple,bool forward)6651 void FakeVimHandler::Private::moveToNextBoundaryEnd(int count, bool simple, bool forward)
6652 {
6653     moveToNextBoundary(true, count, simple, forward);
6654 }
6655 
moveToBoundaryStart(int count,bool simple,bool forward)6656 void FakeVimHandler::Private::moveToBoundaryStart(int count, bool simple, bool forward)
6657 {
6658     moveToNextBoundaryStart(atBoundary(false, simple) ? count - 1 : count, simple, forward);
6659 }
6660 
moveToBoundaryEnd(int count,bool simple,bool forward)6661 void FakeVimHandler::Private::moveToBoundaryEnd(int count, bool simple, bool forward)
6662 {
6663     moveToNextBoundaryEnd(atBoundary(true, simple) ? count - 1 : count, simple, forward);
6664 }
6665 
moveToNextWord(bool end,int count,bool simple,bool forward,bool emptyLines)6666 void FakeVimHandler::Private::moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines)
6667 {
6668     int repeat = count;
6669     while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
6670         setPosition(position() + (forward ? 1 : -1));
6671         moveToBoundary(simple, forward);
6672         if (atWordBoundary(end, simple) && (emptyLines || !atEmptyLine()) )
6673             --repeat;
6674     }
6675 }
6676 
moveToNextWordStart(int count,bool simple,bool forward,bool emptyLines)6677 void FakeVimHandler::Private::moveToNextWordStart(int count, bool simple, bool forward, bool emptyLines)
6678 {
6679     moveToNextWord(false, count, simple, forward, emptyLines);
6680 }
6681 
moveToNextWordEnd(int count,bool simple,bool forward,bool emptyLines)6682 void FakeVimHandler::Private::moveToNextWordEnd(int count, bool simple, bool forward, bool emptyLines)
6683 {
6684     moveToNextWord(true, count, simple, forward, emptyLines);
6685 }
6686 
moveToWordStart(int count,bool simple,bool forward,bool emptyLines)6687 void FakeVimHandler::Private::moveToWordStart(int count, bool simple, bool forward, bool emptyLines)
6688 {
6689     moveToNextWordStart(atWordStart(simple) ? count - 1 : count, simple, forward, emptyLines);
6690 }
6691 
moveToWordEnd(int count,bool simple,bool forward,bool emptyLines)6692 void FakeVimHandler::Private::moveToWordEnd(int count, bool simple, bool forward, bool emptyLines)
6693 {
6694     moveToNextWordEnd(atWordEnd(simple) ? count - 1 : count, simple, forward, emptyLines);
6695 }
6696 
handleFfTt(const QString & key,bool repeats)6697 bool FakeVimHandler::Private::handleFfTt(const QString &key, bool repeats)
6698 {
6699     int key0 = key.size() == 1 ? key.at(0).unicode() : 0;
6700     // g.subsubmode \in { 'f', 'F', 't', 'T' }
6701     bool forward = g.subsubdata.is('f') || g.subsubdata.is('t');
6702     bool exclusive =  g.subsubdata.is('t') || g.subsubdata.is('T');
6703     int repeat = count();
6704     int n = block().position() + (forward ? block().length() : - 1);
6705     QTextDocument *doc = document();
6706     const int d = forward ? 1 : -1;
6707     // FIXME: This also depends on whether 'cpositions' Vim option contains ';'.
6708     const int skip = (repeats && repeat == 1 && exclusive) ? d : 0;
6709     int pos = position() + d + skip;
6710 
6711     for (; repeat > 0 && (forward ? pos < n : pos > n); pos += d) {
6712         if (doc->characterAt(pos).unicode() == key0)
6713             --repeat;
6714     }
6715 
6716     if (repeat == 0) {
6717         setPosition(pos - d - (exclusive ? d : 0));
6718         setTargetColumn();
6719         return true;
6720     }
6721 
6722     return false;
6723 }
6724 
moveToMatchingParanthesis()6725 void FakeVimHandler::Private::moveToMatchingParanthesis()
6726 {
6727     bool moved = false;
6728     bool forward = false;
6729 
6730     const int anc = anchor();
6731     QTextCursor tc = m_cursor;
6732 
6733     // If no known parenthesis symbol is under cursor find one on the current line after cursor.
6734     static const QString parenthesesChars(_("([{}])"));
6735     while (!parenthesesChars.contains(document()->characterAt(tc.position())) && !tc.atBlockEnd())
6736         tc.setPosition(tc.position() + 1);
6737 
6738     if (tc.atBlockEnd())
6739         tc = m_cursor;
6740 
6741     emit q->moveToMatchingParenthesis(&moved, &forward, &tc);
6742     if (moved) {
6743         if (forward)
6744             tc.movePosition(Left, KeepAnchor, 1);
6745         setAnchorAndPosition(anc, tc.position());
6746         setTargetColumn();
6747     }
6748 }
6749 
cursorLineOnScreen() const6750 int FakeVimHandler::Private::cursorLineOnScreen() const
6751 {
6752     if (!editor())
6753         return 0;
6754     const QRect rect = EDITOR(cursorRect(m_cursor));
6755     return rect.height() > 0 ? rect.y() / rect.height() : 0;
6756 }
6757 
linesOnScreen() const6758 int FakeVimHandler::Private::linesOnScreen() const
6759 {
6760     if (!editor())
6761         return 1;
6762     const int h = EDITOR(cursorRect(m_cursor)).height();
6763     return h > 0 ? EDITOR(viewport()->height()) / h : 1;
6764 }
6765 
cursorLine() const6766 int FakeVimHandler::Private::cursorLine() const
6767 {
6768     return lineForPosition(position()) - 1;
6769 }
6770 
cursorBlockNumber() const6771 int FakeVimHandler::Private::cursorBlockNumber() const
6772 {
6773     return document()->findBlock(qMin(anchor(), position())).blockNumber();
6774 }
6775 
physicalCursorColumn() const6776 int FakeVimHandler::Private::physicalCursorColumn() const
6777 {
6778     return position() - block().position();
6779 }
6780 
physicalToLogicalColumn(const int physical,const QString & line) const6781 int FakeVimHandler::Private::physicalToLogicalColumn
6782     (const int physical, const QString &line) const
6783 {
6784     const int ts = config(ConfigTabStop).toInt();
6785     int p = 0;
6786     int logical = 0;
6787     while (p < physical) {
6788         QChar c = line.at(p);
6789         //if (c == QLatin1Char(' '))
6790         //    ++logical;
6791         //else
6792         if (c == QLatin1Char('\t'))
6793             logical += ts - logical % ts;
6794         else
6795             ++logical;
6796             //break;
6797         ++p;
6798     }
6799     return logical;
6800 }
6801 
logicalToPhysicalColumn(const int logical,const QString & line) const6802 int FakeVimHandler::Private::logicalToPhysicalColumn
6803     (const int logical, const QString &line) const
6804 {
6805     const int ts = config(ConfigTabStop).toInt();
6806     int physical = 0;
6807     for (int l = 0; l < logical && physical < line.size(); ++physical) {
6808         QChar c = line.at(physical);
6809         if (c == QLatin1Char('\t'))
6810             l += ts - l % ts;
6811         else
6812             ++l;
6813     }
6814     return physical;
6815 }
6816 
windowScrollOffset() const6817 int FakeVimHandler::Private::windowScrollOffset() const
6818 {
6819     return qMin(theFakeVimSetting(ConfigScrollOff)->value().toInt(), linesOnScreen() / 2);
6820 }
6821 
logicalCursorColumn() const6822 int FakeVimHandler::Private::logicalCursorColumn() const
6823 {
6824     const int physical = physicalCursorColumn();
6825     const QString line = block().text();
6826     return physicalToLogicalColumn(physical, line);
6827 }
6828 
cursorColumn() const6829 Column FakeVimHandler::Private::cursorColumn() const
6830 {
6831     return Column(physicalCursorColumn(), logicalCursorColumn());
6832 }
6833 
linesInDocument() const6834 int FakeVimHandler::Private::linesInDocument() const
6835 {
6836     if (m_cursor.isNull())
6837         return 0;
6838     return document()->blockCount();
6839 }
6840 
scrollToLine(int line)6841 void FakeVimHandler::Private::scrollToLine(int line)
6842 {
6843     // Don't scroll if the line is already at the top.
6844     updateFirstVisibleLine();
6845     if (line == m_firstVisibleLine)
6846         return;
6847 
6848     const QTextCursor tc = m_cursor;
6849 
6850     QTextCursor tc2 = tc;
6851     tc2.setPosition(document()->lastBlock().position());
6852     EDITOR(setTextCursor(tc2));
6853     EDITOR(ensureCursorVisible());
6854 
6855     int offset = 0;
6856     const QTextBlock block = document()->findBlockByLineNumber(line);
6857     if (block.isValid()) {
6858         const int blockLineCount = block.layout()->lineCount();
6859         const int lineInBlock = line - block.firstLineNumber();
6860         if (0 <= lineInBlock && lineInBlock < blockLineCount) {
6861             QTextLine textLine = block.layout()->lineAt(lineInBlock);
6862             offset = textLine.textStart();
6863         } else {
6864 //            QTC_CHECK(false);
6865         }
6866     }
6867     tc2.setPosition(block.position() + offset);
6868     EDITOR(setTextCursor(tc2));
6869     EDITOR(ensureCursorVisible());
6870 
6871     EDITOR(setTextCursor(tc));
6872 
6873     m_firstVisibleLine = line;
6874 }
6875 
updateFirstVisibleLine()6876 void FakeVimHandler::Private::updateFirstVisibleLine()
6877 {
6878     const QTextCursor tc = EDITOR(cursorForPosition(QPoint(0,0)));
6879     m_firstVisibleLine = lineForPosition(tc.position()) - 1;
6880 }
6881 
firstVisibleLine() const6882 int FakeVimHandler::Private::firstVisibleLine() const
6883 {
6884     return m_firstVisibleLine;
6885 }
6886 
lastVisibleLine() const6887 int FakeVimHandler::Private::lastVisibleLine() const
6888 {
6889     const int line = m_firstVisibleLine + linesOnScreen();
6890     const QTextBlock block = document()->findBlockByLineNumber(line);
6891     return block.isValid() ? line : document()->lastBlock().firstLineNumber();
6892 }
6893 
lineOnTop(int count) const6894 int FakeVimHandler::Private::lineOnTop(int count) const
6895 {
6896     const int scrollOffset = qMax(count - 1, windowScrollOffset());
6897     const int line = firstVisibleLine();
6898     return line == 0 ? count - 1 : scrollOffset + line;
6899 }
6900 
lineOnBottom(int count) const6901 int FakeVimHandler::Private::lineOnBottom(int count) const
6902 {
6903     const int scrollOffset = qMax(count - 1, windowScrollOffset());
6904     const int line = lastVisibleLine();
6905     return line >= document()->lastBlock().firstLineNumber() ? line - count + 1
6906                                                              : line - scrollOffset - 1;
6907 }
6908 
scrollUp(int count)6909 void FakeVimHandler::Private::scrollUp(int count)
6910 {
6911     scrollToLine(cursorLine() - cursorLineOnScreen() - count);
6912 }
6913 
updateScrollOffset()6914 void FakeVimHandler::Private::updateScrollOffset()
6915 {
6916     const int line = cursorLine();
6917     if (line < lineOnTop())
6918         scrollToLine(qMax(0, line - windowScrollOffset()));
6919     else if (line > lineOnBottom())
6920         scrollToLine(firstVisibleLine() + line - lineOnBottom());
6921 }
6922 
alignViewportToCursor(AlignmentFlag align,int line,bool moveToNonBlank)6923 void FakeVimHandler::Private::alignViewportToCursor(AlignmentFlag align, int line,
6924     bool moveToNonBlank)
6925 {
6926     if (line > 0)
6927         setPosition(firstPositionInLine(line));
6928     if (moveToNonBlank)
6929         moveToFirstNonBlankOnLine();
6930 
6931     if (align == Qt::AlignTop)
6932         scrollUp(- cursorLineOnScreen());
6933     else if (align == Qt::AlignVCenter)
6934         scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
6935     else if (align == Qt::AlignBottom)
6936         scrollUp(linesOnScreen() - cursorLineOnScreen() - 1);
6937 }
6938 
lineToBlockNumber(int line) const6939 int FakeVimHandler::Private::lineToBlockNumber(int line) const
6940 {
6941     return document()->findBlockByLineNumber(line).blockNumber();
6942 }
6943 
setCursorPosition(const CursorPosition & p)6944 void FakeVimHandler::Private::setCursorPosition(const CursorPosition &p)
6945 {
6946     const int firstLine = firstVisibleLine();
6947     const int firstBlock = lineToBlockNumber(firstLine);
6948     const int lastBlock = lineToBlockNumber(firstLine + linesOnScreen() - 2);
6949     bool isLineVisible = firstBlock <= p.line && p.line <= lastBlock;
6950     setCursorPosition(&m_cursor, p);
6951     if (!isLineVisible)
6952         alignViewportToCursor(Qt::AlignVCenter);
6953 }
6954 
setCursorPosition(QTextCursor * tc,const CursorPosition & p)6955 void FakeVimHandler::Private::setCursorPosition(QTextCursor *tc, const CursorPosition &p)
6956 {
6957     const int line = qMin(document()->blockCount() - 1, p.line);
6958     QTextBlock block = document()->findBlockByNumber(line);
6959     const int column = qMin(p.column, block.length() - 1);
6960     tc->setPosition(block.position() + column, KeepAnchor);
6961 }
6962 
lastPositionInDocument(bool ignoreMode) const6963 int FakeVimHandler::Private::lastPositionInDocument(bool ignoreMode) const
6964 {
6965     return document()->characterCount()
6966         - (ignoreMode || isVisualMode() || isInsertMode() ? 1 : 2);
6967 }
6968 
selectText(const Range & range) const6969 QString FakeVimHandler::Private::selectText(const Range &range) const
6970 {
6971     if (range.rangemode == RangeCharMode) {
6972         QTextCursor tc(document());
6973         tc.setPosition(range.beginPos, MoveAnchor);
6974         tc.setPosition(range.endPos, KeepAnchor);
6975         return tc.selection().toPlainText();
6976     }
6977     if (range.rangemode == RangeLineMode) {
6978         QTextCursor tc(document());
6979         int firstPos = firstPositionInLine(lineForPosition(range.beginPos));
6980         int lastLine = lineForPosition(range.endPos);
6981         bool endOfDoc = lastLine == lineNumber(document()->lastBlock());
6982         int lastPos = endOfDoc ? lastPositionInDocument(true) : firstPositionInLine(lastLine + 1);
6983         tc.setPosition(firstPos, MoveAnchor);
6984         tc.setPosition(lastPos, KeepAnchor);
6985         return tc.selection().toPlainText() + _(endOfDoc? "\n" : "");
6986     }
6987     // FIXME: Performance?
6988     int beginLine = lineForPosition(range.beginPos);
6989     int endLine = lineForPosition(range.endPos);
6990     int beginColumn = 0;
6991     int endColumn = INT_MAX;
6992     if (range.rangemode == RangeBlockMode) {
6993         int column1 = range.beginPos - firstPositionInLine(beginLine);
6994         int column2 = range.endPos - firstPositionInLine(endLine);
6995         beginColumn = qMin(column1, column2);
6996         endColumn = qMax(column1, column2);
6997     }
6998     int len = endColumn - beginColumn + 1;
6999     QString contents;
7000     QTextBlock block = document()->findBlockByLineNumber(beginLine - 1);
7001     for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
7002         QString line = block.text();
7003         if (range.rangemode == RangeBlockMode) {
7004             line = line.mid(beginColumn, len);
7005             if (line.size() < len)
7006                 line += QString(len - line.size(), QLatin1Char(' '));
7007         }
7008         contents += line;
7009         if (!contents.endsWith(QLatin1Char('\n')))
7010             contents += QLatin1Char('\n');
7011         block = block.next();
7012     }
7013     //qDebug() << "SELECTED: " << contents;
7014     return contents;
7015 }
7016 
yankText(const Range & range,int reg)7017 void FakeVimHandler::Private::yankText(const Range &range, int reg)
7018 {
7019     const QString text = selectText(range);
7020     setRegister(reg, text, range.rangemode);
7021 
7022     // If register is not specified or " ...
7023     if (m_register == '"') {
7024         // copy to yank register 0 too
7025         setRegister('0', text, range.rangemode);
7026 
7027         // with delete and change commands set register 1 (if text contains more lines) or
7028         // small delete register -
7029         if (g.submode == DeleteSubMode || g.submode == ChangeSubMode) {
7030             if (text.contains(QLatin1Char('\n')))
7031                 setRegister('1', text, range.rangemode);
7032             else
7033                 setRegister('-', text, range.rangemode);
7034         }
7035     } else {
7036         // Always copy to " register too.
7037         setRegister('"', text, range.rangemode);
7038     }
7039 
7040     const int lines = document()->findBlock(range.endPos).blockNumber()
7041         - document()->findBlock(range.beginPos).blockNumber() + 1;
7042     if (lines > 2)
7043         showMessage(MessageInfo, FakeVimHandler::tr("%n lines yanked.", 0, lines));
7044 }
7045 
transformText(const Range & range,Transformation transformFunc,const QVariant & extra)7046 void FakeVimHandler::Private::transformText(const Range &range,
7047     Transformation transformFunc, const QVariant &extra)
7048 {
7049     QTextCursor tc = m_cursor;
7050     int posAfter = range.beginPos;
7051     switch (range.rangemode) {
7052         case RangeCharMode: {
7053             // This can span multiple lines.
7054             beginEditBlock();
7055             tc.setPosition(range.beginPos, MoveAnchor);
7056             tc.setPosition(range.endPos, KeepAnchor);
7057             TransformationData td(tc.selectedText(), extra);
7058             (this->*transformFunc)(&td);
7059             insertText(tc, td.to);
7060             endEditBlock();
7061             break;
7062         }
7063         case RangeLineMode:
7064         case RangeLineModeExclusive: {
7065             beginEditBlock();
7066             tc.setPosition(range.beginPos, MoveAnchor);
7067             tc.movePosition(StartOfLine, MoveAnchor);
7068             tc.setPosition(range.endPos, KeepAnchor);
7069             tc.movePosition(EndOfLine, KeepAnchor);
7070             if (range.rangemode != RangeLineModeExclusive) {
7071                 // make sure that complete lines are removed
7072                 // - also at the beginning and at the end of the document
7073                 if (tc.atEnd()) {
7074                     tc.setPosition(range.beginPos, MoveAnchor);
7075                     tc.movePosition(StartOfLine, MoveAnchor);
7076                     if (!tc.atStart()) {
7077                         // also remove first line if it is the only one
7078                         tc.movePosition(Left, MoveAnchor, 1);
7079                         tc.movePosition(EndOfLine, MoveAnchor, 1);
7080                     }
7081                     tc.setPosition(range.endPos, KeepAnchor);
7082                     tc.movePosition(EndOfLine, KeepAnchor);
7083                 } else {
7084                     tc.movePosition(Right, KeepAnchor, 1);
7085                 }
7086             }
7087             TransformationData td(tc.selectedText(), extra);
7088             (this->*transformFunc)(&td);
7089             posAfter = tc.anchor();
7090             insertText(tc, td.to);
7091             endEditBlock();
7092             break;
7093         }
7094         case RangeBlockAndTailMode:
7095         case RangeBlockMode: {
7096             int beginLine = lineForPosition(range.beginPos);
7097             int endLine = lineForPosition(range.endPos);
7098             int column1 = range.beginPos - firstPositionInLine(beginLine);
7099             int column2 = range.endPos - firstPositionInLine(endLine);
7100             int beginColumn = qMin(column1, column2);
7101             int endColumn = qMax(column1, column2);
7102             if (range.rangemode == RangeBlockAndTailMode)
7103                 endColumn = INT_MAX - 1;
7104             QTextBlock block = document()->findBlockByLineNumber(endLine - 1);
7105             beginEditBlock();
7106             for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
7107                 int bCol = qMin(beginColumn, block.length() - 1);
7108                 int eCol = qMin(endColumn + 1, block.length() - 1);
7109                 tc.setPosition(block.position() + bCol, MoveAnchor);
7110                 tc.setPosition(block.position() + eCol, KeepAnchor);
7111                 TransformationData td(tc.selectedText(), extra);
7112                 (this->*transformFunc)(&td);
7113                 insertText(tc, td.to);
7114                 block = block.previous();
7115             }
7116             endEditBlock();
7117             break;
7118         }
7119     }
7120 
7121     setPosition(posAfter);
7122     setTargetColumn();
7123 }
7124 
insertText(QTextCursor & tc,const QString & text)7125 void FakeVimHandler::Private::insertText(QTextCursor &tc, const QString &text)
7126 {
7127   if (hasConfig(ConfigPassKeys)) {
7128       QTextCursor oldTc = m_cursor;
7129       m_cursor = tc;
7130 
7131       if (tc.hasSelection() && text.isEmpty()) {
7132           QKeyEvent event(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier, QString());
7133           passEventToEditor(event);
7134       }
7135 
7136       foreach (QChar c, text) {
7137           QKeyEvent event(QEvent::KeyPress, -1, Qt::NoModifier, QString(c));
7138           passEventToEditor(event);
7139       }
7140 
7141       tc = m_cursor;
7142       m_cursor = oldTc;
7143   } else {
7144       tc.insertText(text);
7145   }
7146 }
7147 
insertText(const Register & reg)7148 void FakeVimHandler::Private::insertText(const Register &reg)
7149 {
7150     QTC_ASSERT(reg.rangemode == RangeCharMode,
7151         qDebug() << "WRONG INSERT MODE: " << reg.rangemode; return);
7152     setAnchor();
7153     m_cursor.insertText(reg.contents);
7154     //dump("AFTER INSERT");
7155 }
7156 
removeText(const Range & range)7157 void FakeVimHandler::Private::removeText(const Range &range)
7158 {
7159     //qDebug() << "REMOVE: " << range;
7160     transformText(range, &FakeVimHandler::Private::removeTransform);
7161 }
7162 
removeTransform(TransformationData * td)7163 void FakeVimHandler::Private::removeTransform(TransformationData *td)
7164 {
7165     Q_UNUSED(td);
7166 }
7167 
downCase(const Range & range)7168 void FakeVimHandler::Private::downCase(const Range &range)
7169 {
7170     transformText(range, &FakeVimHandler::Private::downCaseTransform);
7171 }
7172 
downCaseTransform(TransformationData * td)7173 void FakeVimHandler::Private::downCaseTransform(TransformationData *td)
7174 {
7175     td->to = td->from.toLower();
7176 }
7177 
upCase(const Range & range)7178 void FakeVimHandler::Private::upCase(const Range &range)
7179 {
7180     transformText(range, &FakeVimHandler::Private::upCaseTransform);
7181 }
7182 
upCaseTransform(TransformationData * td)7183 void FakeVimHandler::Private::upCaseTransform(TransformationData *td)
7184 {
7185     td->to = td->from.toUpper();
7186 }
7187 
invertCase(const Range & range)7188 void FakeVimHandler::Private::invertCase(const Range &range)
7189 {
7190     transformText(range, &FakeVimHandler::Private::invertCaseTransform);
7191 }
7192 
invertCaseTransform(TransformationData * td)7193 void FakeVimHandler::Private::invertCaseTransform(TransformationData *td)
7194 {
7195     foreach (QChar c, td->from)
7196         td->to += c.isUpper() ? c.toLower() : c.toUpper();
7197 }
7198 
replaceText(const Range & range,const QString & str)7199 void FakeVimHandler::Private::replaceText(const Range &range, const QString &str)
7200 {
7201     Transformation tr = &FakeVimHandler::Private::replaceByStringTransform;
7202     transformText(range, tr, str);
7203 }
7204 
replaceByStringTransform(TransformationData * td)7205 void FakeVimHandler::Private::replaceByStringTransform(TransformationData *td)
7206 {
7207     td->to = td->extraData.toString();
7208 }
7209 
replaceByCharTransform(TransformationData * td)7210 void FakeVimHandler::Private::replaceByCharTransform(TransformationData *td)
7211 {
7212     // Replace each character but preserve lines.
7213     const int len = td->from.size();
7214     td->to = QString(len, td->extraData.toChar());
7215     for (int i = 0; i < len; ++i) {
7216         if (td->from.at(i) == ParagraphSeparator)
7217             td->to[i] = ParagraphSeparator;
7218     }
7219 }
7220 
pasteText(bool afterCursor)7221 void FakeVimHandler::Private::pasteText(bool afterCursor)
7222 {
7223     const QString text = registerContents(m_register);
7224     const RangeMode rangeMode = registerRangeMode(m_register);
7225 
7226     beginEditBlock();
7227 
7228     // In visual mode paste text only inside selection.
7229     bool pasteAfter = isVisualMode() ? false : afterCursor;
7230 
7231     bool visualCharMode = isVisualCharMode();
7232     if (visualCharMode) {
7233         leaveVisualMode();
7234         g.rangemode = RangeCharMode;
7235         Range range = currentRange();
7236         range.endPos++;
7237         yankText(range, m_register);
7238         removeText(range);
7239     } else if (isVisualLineMode()) {
7240         leaveVisualMode();
7241         g.rangemode = RangeLineMode;
7242         Range range = currentRange();
7243         range.endPos++;
7244         yankText(range, m_register);
7245         removeText(range);
7246         handleStartOfLine();
7247     } else if (isVisualBlockMode()) {
7248         leaveVisualMode();
7249         g.rangemode = RangeBlockMode;
7250         Range range = currentRange();
7251         yankText(range, m_register);
7252         removeText(range);
7253         setPosition(qMin(position(), anchor()));
7254     }
7255 
7256     switch (rangeMode) {
7257         case RangeCharMode: {
7258             m_targetColumn = 0;
7259             const int pos = position() + 1;
7260             if (pasteAfter && rightDist() > 0)
7261                 moveRight();
7262             insertText(text.repeated(count()));
7263             if (text.contains(QLatin1Char('\n')))
7264                 setPosition(pos);
7265             else
7266                 moveLeft();
7267             break;
7268         }
7269         case RangeLineMode:
7270         case RangeLineModeExclusive: {
7271             QTextCursor tc = m_cursor;
7272             if (visualCharMode)
7273                 tc.insertBlock();
7274             else
7275                 moveToStartOfLine();
7276             m_targetColumn = 0;
7277             bool lastLine = false;
7278             if (pasteAfter) {
7279                 lastLine = document()->lastBlock() == this->block();
7280                 if (lastLine) {
7281                     tc.movePosition(EndOfLine, MoveAnchor);
7282                     tc.insertBlock();
7283                 }
7284                 moveDown();
7285             }
7286             const int pos = position();
7287             if (lastLine)
7288                 insertText(text.repeated(count()).left(text.size() * count() - 1));
7289             else
7290                 insertText(text.repeated(count()));
7291             setPosition(pos);
7292             moveToFirstNonBlankOnLine();
7293             break;
7294         }
7295         case RangeBlockAndTailMode:
7296         case RangeBlockMode: {
7297             const int pos = position();
7298             if (pasteAfter && rightDist() > 0)
7299                 moveRight();
7300             QTextCursor tc = m_cursor;
7301             const int col = tc.columnNumber();
7302             QTextBlock block = tc.block();
7303             const QStringList lines = text.split(QLatin1Char('\n'));
7304             for (int i = 0; i < lines.size() - 1; ++i) {
7305                 if (!block.isValid()) {
7306                     tc.movePosition(EndOfDocument);
7307                     tc.insertBlock();
7308                     block = tc.block();
7309                 }
7310 
7311                 // resize line
7312                 int length = block.length();
7313                 int begin = block.position();
7314                 if (col >= length) {
7315                     tc.setPosition(begin + length - 1);
7316                     tc.insertText(QString(col - length + 1, QLatin1Char(' ')));
7317                 } else {
7318                     tc.setPosition(begin + col);
7319                 }
7320 
7321                 // insert text
7322                 const QString line = lines.at(i).repeated(count());
7323                 tc.insertText(line);
7324 
7325                 // next line
7326                 block = block.next();
7327             }
7328             setPosition(pos);
7329             if (pasteAfter)
7330                 moveRight();
7331             break;
7332         }
7333     }
7334 
7335     endEditBlock();
7336 }
7337 
joinLines(int count,bool preserveSpace)7338 void FakeVimHandler::Private::joinLines(int count, bool preserveSpace)
7339 {
7340     int pos = position();
7341     const int blockNumber = m_cursor.blockNumber();
7342     for (int i = qMax(count - 2, 0); i >= 0 && blockNumber < document()->blockCount(); --i) {
7343         moveBehindEndOfLine();
7344         pos = position();
7345         setAnchor();
7346         moveRight();
7347         if (preserveSpace) {
7348             removeText(currentRange());
7349         } else {
7350             while (characterAtCursor() == QLatin1Char(' ') || characterAtCursor() == QLatin1Char('\t'))
7351                 moveRight();
7352             m_cursor.insertText(QString(QLatin1Char(' ')));
7353         }
7354     }
7355     setPosition(pos);
7356 }
7357 
insertNewLine()7358 void FakeVimHandler::Private::insertNewLine()
7359 {
7360     if ( m_buffer->editBlockLevel <= 1 && hasConfig(ConfigPassKeys) ) {
7361         QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, QLatin1String("\n"));
7362         if (passEventToEditor(event))
7363             return;
7364     }
7365 
7366     insertText(QString::fromLatin1("\n"));
7367     insertAutomaticIndentation(true);
7368 }
7369 
handleInsertInEditor(const Input & input)7370 bool FakeVimHandler::Private::handleInsertInEditor(const Input &input)
7371 {
7372     if (m_buffer->editBlockLevel > 0 || !hasConfig(ConfigPassKeys))
7373         return false;
7374 
7375     joinPreviousEditBlock();
7376 
7377     QKeyEvent event(QEvent::KeyPress, input.key(), input.modifiers(), input.text());
7378     setAnchor();
7379     if (!passEventToEditor(event))
7380         return !m_textedit && !m_plaintextedit; // Mark event as handled if it has destroyed editor.
7381 
7382     endEditBlock();
7383 
7384     return true;
7385 }
7386 
passEventToEditor(QEvent & event)7387 bool FakeVimHandler::Private::passEventToEditor(QEvent &event)
7388 {
7389     removeEventFilter();
7390 
7391     EDITOR(setOverwriteMode(false));
7392     commitCursor();
7393     bool accepted = QApplication::sendEvent(editor(), &event);
7394     if (!m_textedit && !m_plaintextedit)
7395         return false;
7396     updateCursorShape();
7397 
7398     if (accepted)
7399         pullCursor();
7400 
7401     return accepted;
7402 }
7403 
lineContents(int line) const7404 QString FakeVimHandler::Private::lineContents(int line) const
7405 {
7406     return document()->findBlockByLineNumber(line - 1).text();
7407 }
7408 
textAt(int from,int to) const7409 QString FakeVimHandler::Private::textAt(int from, int to) const
7410 {
7411     QTextCursor tc(document());
7412     tc.setPosition(from);
7413     tc.setPosition(to, KeepAnchor);
7414     return tc.selectedText().replace(ParagraphSeparator, QLatin1Char('\n'));
7415 }
7416 
setLineContents(int line,const QString & contents)7417 void FakeVimHandler::Private::setLineContents(int line, const QString &contents)
7418 {
7419     QTextBlock block = document()->findBlockByLineNumber(line - 1);
7420     QTextCursor tc = m_cursor;
7421     const int begin = block.position();
7422     const int len = block.length();
7423     tc.setPosition(begin);
7424     tc.setPosition(begin + len - 1, KeepAnchor);
7425     tc.insertText(contents);
7426 }
7427 
blockBoundary(const QString & left,const QString & right,bool closing,int count) const7428 int FakeVimHandler::Private::blockBoundary(const QString &left,
7429     const QString &right, bool closing, int count) const
7430 {
7431     const QString &begin = closing ? left : right;
7432     const QString &end = closing ? right : left;
7433 
7434     // shift cursor if it is already on opening/closing string
7435     QTextCursor tc1 = m_cursor;
7436     int pos = tc1.position();
7437     int max = document()->characterCount();
7438     int sz = left.size();
7439     int from = qMax(pos - sz + 1, 0);
7440     int to = qMin(pos + sz, max);
7441     tc1.setPosition(from);
7442     tc1.setPosition(to, KeepAnchor);
7443     int i = tc1.selectedText().indexOf(left);
7444     if (i != -1) {
7445         // - on opening string:
7446         tc1.setPosition(from + i + sz);
7447     } else {
7448         sz = right.size();
7449         from = qMax(pos - sz + 1, 0);
7450         to = qMin(pos + sz, max);
7451         tc1.setPosition(from);
7452         tc1.setPosition(to, KeepAnchor);
7453         i = tc1.selectedText().indexOf(right);
7454         if (i != -1) {
7455             // - on closing string:
7456             tc1.setPosition(from + i);
7457         } else {
7458             tc1 = m_cursor;
7459         }
7460     }
7461 
7462     QTextCursor tc2 = tc1;
7463     QTextDocument::FindFlags flags(closing ? 0 : QTextDocument::FindBackward);
7464     int level = 0;
7465     int counter = 0;
7466     while (true) {
7467         tc2 = document()->find(end, tc2, flags);
7468         if (tc2.isNull())
7469             return -1;
7470         if (!tc1.isNull())
7471             tc1 = document()->find(begin, tc1, flags);
7472 
7473         while (!tc1.isNull() && (closing ? (tc1 < tc2) : (tc2 < tc1))) {
7474             ++level;
7475             tc1 = document()->find(begin, tc1, flags);
7476         }
7477 
7478         while (level > 0
7479                && (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
7480             --level;
7481             tc2 = document()->find(end, tc2, flags);
7482             if (tc2.isNull())
7483                 return -1;
7484         }
7485 
7486         if (level == 0
7487             && (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
7488             ++counter;
7489             if (counter >= count)
7490                 break;
7491         }
7492     }
7493 
7494     return tc2.position() - end.size();
7495 }
7496 
lineNumber(const QTextBlock & block) const7497 int FakeVimHandler::Private::lineNumber(const QTextBlock &block) const
7498 {
7499     if (block.isVisible())
7500         return block.firstLineNumber() + 1;
7501 
7502     // Folded block has line number of the nearest previous visible line.
7503     QTextBlock block2 = block;
7504     while (block2.isValid() && !block2.isVisible())
7505         block2 = block2.previous();
7506     return block2.firstLineNumber() + 1;
7507 }
7508 
nextLine(const QTextBlock & block) const7509 QTextBlock FakeVimHandler::Private::nextLine(const QTextBlock &block) const
7510 {
7511     return document()->findBlock(block.position() + block.length());
7512 }
7513 
previousLine(const QTextBlock & block) const7514 QTextBlock FakeVimHandler::Private::previousLine(const QTextBlock &block) const
7515 {
7516     return document()->findBlock(block.position() - 1);
7517 }
7518 
firstPositionInLine(int line,bool onlyVisibleLines) const7519 int FakeVimHandler::Private::firstPositionInLine(int line, bool onlyVisibleLines) const
7520 {
7521     QTextBlock block = onlyVisibleLines ? document()->findBlockByLineNumber(line - 1)
7522         : document()->findBlockByNumber(line - 1);
7523     return block.position();
7524 }
7525 
lastPositionInLine(int line,bool onlyVisibleLines) const7526 int FakeVimHandler::Private::lastPositionInLine(int line, bool onlyVisibleLines) const
7527 {
7528     QTextBlock block;
7529     if (onlyVisibleLines) {
7530         block = document()->findBlockByLineNumber(line - 1);
7531         // respect folds and wrapped lines
7532         do {
7533             block = nextLine(block);
7534         } while (block.isValid() && !block.isVisible());
7535         if (block.isValid()) {
7536             if (line > 0)
7537                 block = block.previous();
7538         } else {
7539             block = document()->lastBlock();
7540         }
7541     } else {
7542         block = document()->findBlockByNumber(line - 1);
7543     }
7544 
7545     const int position = block.position() + block.length() - 1;
7546     if (block.length() > 1 && !isVisualMode() && !isInsertMode())
7547         return position - 1;
7548     return position;
7549 }
7550 
lineForPosition(int pos) const7551 int FakeVimHandler::Private::lineForPosition(int pos) const
7552 {
7553     const QTextBlock block = document()->findBlock(pos);
7554     if (!block.isValid())
7555         return 0;
7556     const int positionInBlock = pos - block.position();
7557     const int lineNumberInBlock = block.layout()->lineForTextPosition(positionInBlock).lineNumber();
7558     return block.firstLineNumber() + lineNumberInBlock + 1;
7559 }
7560 
toggleVisualMode(VisualMode visualMode)7561 void FakeVimHandler::Private::toggleVisualMode(VisualMode visualMode)
7562 {
7563     if (visualMode == g.visualMode) {
7564         leaveVisualMode();
7565     } else {
7566         m_positionPastEnd = false;
7567         m_anchorPastEnd = false;
7568         g.visualMode = visualMode;
7569         m_buffer->lastVisualMode = visualMode;
7570         updateMiniBuffer();
7571     }
7572 }
7573 
leaveVisualMode()7574 void FakeVimHandler::Private::leaveVisualMode()
7575 {
7576     if (!isVisualMode())
7577         return;
7578 
7579     setMark(QLatin1Char('<'), markLessPosition());
7580     setMark(QLatin1Char('>'), markGreaterPosition());
7581     m_buffer->lastVisualModeInverted = anchor() > position();
7582     if (isVisualLineMode()) {
7583         g.rangemode = RangeLineMode;
7584         g.movetype = MoveLineWise;
7585     } else if (isVisualCharMode()) {
7586         g.rangemode = RangeCharMode;
7587         g.movetype = MoveInclusive;
7588     } else if (isVisualBlockMode()) {
7589         g.rangemode = RangeBlockMode;
7590         g.movetype = MoveInclusive;
7591     }
7592 
7593     g.visualMode = NoVisualMode;
7594     updateMiniBuffer();
7595 }
7596 
editor() const7597 QWidget *FakeVimHandler::Private::editor() const
7598 {
7599     return m_textedit
7600         ? static_cast<QWidget *>(m_textedit)
7601         : static_cast<QWidget *>(m_plaintextedit);
7602 }
7603 
joinPreviousEditBlock()7604 void FakeVimHandler::Private::joinPreviousEditBlock()
7605 {
7606     UNDO_DEBUG("JOIN");
7607     if (m_buffer->breakEditBlock) {
7608         beginEditBlock();
7609         QTextCursor tc(m_cursor);
7610         tc.setPosition(tc.position());
7611         tc.beginEditBlock();
7612         tc.insertText(_("X"));
7613         tc.deletePreviousChar();
7614         tc.endEditBlock();
7615         m_buffer->breakEditBlock = false;
7616     } else {
7617         if (m_buffer->editBlockLevel == 0 && !m_buffer->undo.empty())
7618             m_buffer->undoState = m_buffer->undo.pop();
7619         beginEditBlock();
7620     }
7621 }
7622 
beginEditBlock(bool largeEditBlock)7623 void FakeVimHandler::Private::beginEditBlock(bool largeEditBlock)
7624 {
7625     UNDO_DEBUG("BEGIN EDIT BLOCK" << m_buffer->editBlockLevel + 1);
7626     if (!largeEditBlock && !m_buffer->undoState.isValid())
7627         pushUndoState(false);
7628     if (m_buffer->editBlockLevel == 0)
7629         m_buffer->breakEditBlock = true;
7630     ++m_buffer->editBlockLevel;
7631 }
7632 
endEditBlock()7633 void FakeVimHandler::Private::endEditBlock()
7634 {
7635     UNDO_DEBUG("END EDIT BLOCK" << m_buffer->editBlockLevel);
7636     QTC_ASSERT(m_buffer->editBlockLevel > 0,
7637         qDebug() << "beginEditBlock() not called before endEditBlock()!"; return);
7638     --m_buffer->editBlockLevel;
7639     if (m_buffer->editBlockLevel == 0 && m_buffer->undoState.isValid()) {
7640         m_buffer->undo.push(m_buffer->undoState);
7641         m_buffer->undoState = State();
7642     }
7643     if (m_buffer->editBlockLevel == 0)
7644         m_buffer->breakEditBlock = false;
7645 }
7646 
onContentsChanged(int position,int charsRemoved,int charsAdded)7647 void FakeVimHandler::Private::onContentsChanged(int position, int charsRemoved, int charsAdded)
7648 {
7649     // Record inserted and deleted text in insert mode.
7650     if (isInsertMode() && (charsAdded > 0 || charsRemoved > 0)) {
7651         BufferData::InsertState &insertState = m_buffer->insertState;
7652         if (!isInsertStateValid()) {
7653             insertState.pos1 = m_oldInternalPosition;
7654             g.dotCommand = _("i");
7655             resetCount();
7656         }
7657 
7658         // Ignore changes outside inserted text (e.g. renaming other occurrences of a variable).
7659         if (position + charsRemoved >= insertState.pos1 && position <= insertState.pos2) {
7660             if (charsRemoved > 0) {
7661                 if (position < insertState.pos1) {
7662                     // backspaces
7663                     const int bs = insertState.pos1 - position;
7664                     const QString inserted = textAt(position, m_oldInternalPosition);
7665                     const QString removed = insertState.textBeforeCursor.right(bs);
7666                     // Ignore backspaces if same text was just inserted.
7667                     if ( !inserted.endsWith(removed) ) {
7668                         insertState.backspaces += bs;
7669                         insertState.pos1 = position;
7670                         insertState.pos2 = qMax(position, insertState.pos2 - bs);
7671                     }
7672                 } else if (position + charsRemoved > insertState.pos2) {
7673                     // deletes
7674                     insertState.deletes += position + charsRemoved - insertState.pos2;
7675                 }
7676             } else if (charsAdded > 0 && insertState.insertingSpaces) {
7677                 for (int i = position; i < position + charsAdded; ++i) {
7678                     const QChar c = document()->characterAt(i);
7679                     if (c.unicode() == ' ' || c.unicode() == '\t')
7680                         insertState.spaces.insert(i);
7681                 }
7682             }
7683 
7684             insertState.pos2 = qMax(insertState.pos2 + charsAdded - charsRemoved,
7685                                       position + charsAdded);
7686             m_oldInternalPosition = position + charsAdded;
7687             insertState.textBeforeCursor = textAt(document()->findBlock(m_oldInternalPosition).position(),
7688                                             m_oldInternalPosition);
7689         }
7690     }
7691 
7692     if (!m_highlighted.isEmpty())
7693         emit q->highlightMatches(m_highlighted);
7694 }
7695 
onUndoCommandAdded()7696 void FakeVimHandler::Private::onUndoCommandAdded()
7697 {
7698     // Undo commands removed?
7699     UNDO_DEBUG("Undo added" << "previous: REV" << m_buffer->lastRevision);
7700     if (m_buffer->lastRevision >= revision()) {
7701         UNDO_DEBUG("UNDO REMOVED!");
7702         const int removed = m_buffer->lastRevision - revision();
7703         for (int i = m_buffer->undo.size() - 1; i >= 0; --i) {
7704             if ((m_buffer->undo[i].revision -= removed) < 0) {
7705                 m_buffer->undo.remove(0, i + 1);
7706                 break;
7707             }
7708         }
7709     }
7710 
7711     m_buffer->redo.clear();
7712     // External change while FakeVim disabled.
7713     if (m_buffer->editBlockLevel == 0 && !m_buffer->undo.isEmpty() && !isInsertMode())
7714         m_buffer->undo.push(State());
7715 }
7716 
currentModeCode() const7717 char FakeVimHandler::Private::currentModeCode() const
7718 {
7719     if (g.mode == ExMode)
7720         return 'c';
7721     else if (isVisualMode())
7722         return 'v';
7723     else if (isOperatorPending())
7724         return 'o';
7725     else if (g.mode == CommandMode)
7726         return 'n';
7727     else if (g.submode != NoSubMode)
7728         return ' ';
7729     else
7730         return 'i';
7731 }
7732 
undoRedo(bool undo)7733 void FakeVimHandler::Private::undoRedo(bool undo)
7734 {
7735     UNDO_DEBUG((undo ? "UNDO" : "REDO"));
7736 
7737     // FIXME: That's only an approximaxtion. The real solution might
7738     // be to store marks and old userData with QTextBlock setUserData
7739     // and retrieve them afterward.
7740     QStack<State> &stack = undo ? m_buffer->undo : m_buffer->redo;
7741     QStack<State> &stack2 = undo ? m_buffer->redo : m_buffer->undo;
7742 
7743     State state = m_buffer->undoState.isValid() ? m_buffer->undoState
7744                                         : !stack.empty() ? stack.pop() : State();
7745 
7746     CursorPosition lastPos(m_cursor);
7747     if (undo ? !document()->isUndoAvailable() : !document()->isRedoAvailable()) {
7748         const QString msg = undo ? FakeVimHandler::tr("Already at oldest change.")
7749             : FakeVimHandler::tr("Already at newest change.");
7750         showMessage(MessageInfo, msg);
7751         UNDO_DEBUG(msg);
7752         return;
7753     }
7754     clearMessage();
7755 
7756     ++m_buffer->editBlockLevel;
7757 
7758     // Do undo/redo [count] times to reach previous revision.
7759     const int previousRevision = revision();
7760     if (undo) {
7761         do {
7762             EDITOR(undo());
7763         } while (document()->isUndoAvailable() && state.revision >= 0 && state.revision < revision());
7764     } else {
7765         do {
7766             EDITOR(redo());
7767         } while (document()->isRedoAvailable() && state.revision > revision());
7768     }
7769 
7770     --m_buffer->editBlockLevel;
7771 
7772     if (state.isValid()) {
7773         m_buffer->lastChangePosition = state.position;
7774         Marks marks = m_buffer->marks;
7775         marks.swap(state.marks);
7776         updateMarks(marks);
7777         m_buffer->lastVisualMode = state.lastVisualMode;
7778         m_buffer->lastVisualModeInverted = state.lastVisualModeInverted;
7779         setMark(QLatin1Char('\''), lastPos);
7780         setMark(QLatin1Char('`'), lastPos);
7781         setCursorPosition(m_buffer->lastChangePosition);
7782         setAnchor();
7783         state.revision = previousRevision;
7784     } else {
7785         updateFirstVisibleLine();
7786         pullCursor();
7787     }
7788     stack2.push(state);
7789 
7790     setTargetColumn();
7791     if (atEndOfLine())
7792         moveLeft();
7793 
7794     UNDO_DEBUG((undo ? "UNDONE" : "REDONE"));
7795 }
7796 
undo()7797 void FakeVimHandler::Private::undo()
7798 {
7799     undoRedo(true);
7800 }
7801 
redo()7802 void FakeVimHandler::Private::redo()
7803 {
7804     undoRedo(false);
7805 }
7806 
updateCursorShape()7807 void FakeVimHandler::Private::updateCursorShape()
7808 {
7809     bool thinCursor = g.mode == ExMode
7810             || g.subsubmode == SearchSubSubMode
7811             || g.mode == InsertMode
7812             || (isVisualMode() && !isVisualCharMode());
7813     EDITOR(setOverwriteMode(!thinCursor));
7814 }
7815 
enterReplaceMode()7816 void FakeVimHandler::Private::enterReplaceMode()
7817 {
7818     enterInsertOrReplaceMode(ReplaceMode);
7819 }
7820 
enterInsertMode()7821 void FakeVimHandler::Private::enterInsertMode()
7822 {
7823     enterInsertOrReplaceMode(InsertMode);
7824 }
7825 
enterInsertOrReplaceMode(Mode mode)7826 void FakeVimHandler::Private::enterInsertOrReplaceMode(Mode mode)
7827 {
7828     QTC_ASSERT(mode == InsertMode || mode == ReplaceMode, return);
7829     if (g.mode == mode)
7830         return;
7831 
7832     if (mode == InsertMode && g.returnToMode != InsertMode) {
7833         // If entering insert mode from command mode, m_targetColumn shouldn't be -1 (end of line).
7834         if (m_targetColumn == -1)
7835             setTargetColumn();
7836     }
7837 
7838     g.mode = mode;
7839     g.submode = NoSubMode;
7840     g.subsubmode = NoSubSubMode;
7841     g.returnToMode = mode;
7842     clearLastInsertion();
7843 }
7844 
enterVisualInsertMode(QChar command)7845 void FakeVimHandler::Private::enterVisualInsertMode(QChar command)
7846 {
7847     if (isVisualBlockMode()) {
7848         bool append = command == QLatin1Char('A');
7849         bool change = command == QLatin1Char('s') || command == QLatin1Char('c');
7850 
7851         setDotCommand(visualDotCommand() + QString::number(count()) + command);
7852         leaveVisualMode();
7853 
7854         const CursorPosition lastAnchor = markLessPosition();
7855         const CursorPosition lastPosition = markGreaterPosition();
7856         CursorPosition pos(lastAnchor.line,
7857             append ? qMax(lastPosition.column, lastAnchor.column) + 1
7858                    : qMin(lastPosition.column, lastAnchor.column));
7859 
7860         if (append) {
7861             m_visualBlockInsert = m_visualTargetColumn == -1 ? AppendToEndOfLineBlockInsertMode
7862                                                              : AppendBlockInsertMode;
7863         } else if (change) {
7864             m_visualBlockInsert = ChangeBlockInsertMode;
7865             pushUndoState();
7866             beginEditBlock();
7867             Range range(position(), anchor(), RangeBlockMode);
7868             yankText(range, m_register);
7869             removeText(range);
7870             endEditBlock();
7871         } else {
7872             m_visualBlockInsert = InsertBlockInsertMode;
7873         }
7874 
7875         setCursorPosition(pos);
7876         if (m_visualBlockInsert == AppendToEndOfLineBlockInsertMode)
7877             moveBehindEndOfLine();
7878     } else {
7879         m_visualBlockInsert = NoneBlockInsertMode;
7880         leaveVisualMode();
7881         if (command == QLatin1Char('I')) {
7882             setDotCommand(_("%1i"), count());
7883             if (lineForPosition(anchor()) <= lineForPosition(position())) {
7884                 setPosition(qMin(anchor(), position()));
7885                 moveToStartOfLine();
7886             }
7887         } else if (command == QLatin1Char('A')) {
7888             setDotCommand(_("%1a"), count());
7889             if (lineForPosition(anchor()) <= lineForPosition(position())) {
7890                 setPosition(position());
7891                 moveRight(qMin(rightDist(), 1));
7892             } else {
7893                 setPosition(anchor());
7894                 moveToStartOfLine();
7895             }
7896         }
7897     }
7898 
7899     setAnchor();
7900     if (m_visualBlockInsert != ChangeBlockInsertMode)
7901         breakEditBlock();
7902     enterInsertMode();
7903 }
7904 
enterCommandMode(Mode returnToMode)7905 void FakeVimHandler::Private::enterCommandMode(Mode returnToMode)
7906 {
7907     if (isNoVisualMode() && atEndOfLine())
7908         moveLeft();
7909     g.mode = CommandMode;
7910     clearCommandMode();
7911     g.returnToMode = returnToMode;
7912 }
7913 
enterExMode(const QString & contents)7914 void FakeVimHandler::Private::enterExMode(const QString &contents)
7915 {
7916     g.currentMessage.clear();
7917     g.commandBuffer.clear();
7918     if (isVisualMode())
7919         g.commandBuffer.setContents(QString::fromLatin1("'<,'>") + contents, contents.size() + 5);
7920     else
7921         g.commandBuffer.setContents(contents, contents.size());
7922     g.mode = ExMode;
7923     g.submode = NoSubMode;
7924     g.subsubmode = NoSubSubMode;
7925 }
7926 
recordJump(int position)7927 void FakeVimHandler::Private::recordJump(int position)
7928 {
7929     CursorPosition pos = position >= 0 ? CursorPosition(document(), position)
7930                                        : CursorPosition(m_cursor);
7931     setMark(QLatin1Char('\''), pos);
7932     setMark(QLatin1Char('`'), pos);
7933     if (m_buffer->jumpListUndo.isEmpty() || m_buffer->jumpListUndo.top() != pos)
7934         m_buffer->jumpListUndo.push(pos);
7935     m_buffer->jumpListRedo.clear();
7936     UNDO_DEBUG("jumps: " << m_buffer->jumpListUndo);
7937 }
7938 
jump(int distance)7939 void FakeVimHandler::Private::jump(int distance)
7940 {
7941     QStack<CursorPosition> &from = (distance > 0) ? m_buffer->jumpListRedo : m_buffer->jumpListUndo;
7942     QStack<CursorPosition> &to   = (distance > 0) ? m_buffer->jumpListUndo : m_buffer->jumpListRedo;
7943     int len = qMin(qAbs(distance), from.size());
7944     CursorPosition m(m_cursor);
7945     setMark(QLatin1Char('\''), m);
7946     setMark(QLatin1Char('`'), m);
7947     for (int i = 0; i < len; ++i) {
7948         to.push(m);
7949         setCursorPosition(from.top());
7950         from.pop();
7951     }
7952 }
7953 
indentation(const QString & line) const7954 Column FakeVimHandler::Private::indentation(const QString &line) const
7955 {
7956     int ts = config(ConfigTabStop).toInt();
7957     int physical = 0;
7958     int logical = 0;
7959     int n = line.size();
7960     while (physical < n) {
7961         QChar c = line.at(physical);
7962         if (c == QLatin1Char(' '))
7963             ++logical;
7964         else if (c == QLatin1Char('\t'))
7965             logical += ts - logical % ts;
7966         else
7967             break;
7968         ++physical;
7969     }
7970     return Column(physical, logical);
7971 }
7972 
tabExpand(int n) const7973 QString FakeVimHandler::Private::tabExpand(int n) const
7974 {
7975     int ts = config(ConfigTabStop).toInt();
7976     if (hasConfig(ConfigExpandTab) || ts < 1)
7977         return QString(n, QLatin1Char(' '));
7978     return QString(n / ts, QLatin1Char('\t'))
7979          + QString(n % ts, QLatin1Char(' '));
7980 }
7981 
insertAutomaticIndentation(bool goingDown,bool forceAutoIndent)7982 void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool forceAutoIndent)
7983 {
7984     if (!forceAutoIndent && !hasConfig(ConfigAutoIndent) && !hasConfig(ConfigSmartIndent))
7985         return;
7986 
7987     if (hasConfig(ConfigSmartIndent)) {
7988         QTextBlock bl = block();
7989         Range range(bl.position(), bl.position());
7990         indentText(range, QLatin1Char('\n'));
7991     } else {
7992         QTextBlock bl = goingDown ? block().previous() : block().next();
7993         QString text = bl.text();
7994         int pos = 0;
7995         int n = text.size();
7996         while (pos < n && text.at(pos).isSpace())
7997             ++pos;
7998         text.truncate(pos);
7999         // FIXME: handle 'smartindent' and 'cindent'
8000         insertText(text);
8001     }
8002 }
8003 
handleStartOfLine()8004 void FakeVimHandler::Private::handleStartOfLine()
8005 {
8006     if (hasConfig(ConfigStartOfLine))
8007         moveToFirstNonBlankOnLine();
8008 }
8009 
replay(const QString & command,int repeat)8010 void FakeVimHandler::Private::replay(const QString &command, int repeat)
8011 {
8012     if (repeat <= 0)
8013         return;
8014 
8015     //qDebug() << "REPLAY: " << quoteUnprintable(command);
8016     clearCommandMode();
8017     Inputs inputs(command);
8018     for (int i = 0; i < repeat; ++i) {
8019         foreach (const Input &in, inputs) {
8020             if (handleDefaultKey(in) != EventHandled)
8021                 return;
8022         }
8023     }
8024 }
8025 
visualDotCommand() const8026 QString FakeVimHandler::Private::visualDotCommand() const
8027 {
8028     QTextCursor start(m_cursor);
8029     QTextCursor end(start);
8030     end.setPosition(end.anchor());
8031 
8032     QString command;
8033 
8034     if (isVisualCharMode())
8035         command = _("v");
8036     else if (isVisualLineMode())
8037         command = _("V");
8038     else if (isVisualBlockMode())
8039         command = _("<c-v>");
8040     else
8041         return QString();
8042 
8043     const int down = qAbs(start.blockNumber() - end.blockNumber());
8044     if (down != 0)
8045         command.append(QString::fromLatin1("%1j").arg(down));
8046 
8047     const int right = start.positionInBlock() - end.positionInBlock();
8048     if (right != 0) {
8049         command.append(QString::number(qAbs(right)));
8050         command.append(QLatin1Char(right < 0 && isVisualBlockMode() ? 'h' : 'l'));
8051     }
8052 
8053     return command;
8054 }
8055 
selectTextObject(bool simple,bool inner)8056 void FakeVimHandler::Private::selectTextObject(bool simple, bool inner)
8057 {
8058     const int position1 = this->position();
8059     const int anchor1 = this->anchor();
8060     bool setupAnchor = (position1 == anchor1);
8061     bool forward = anchor1 <= position1;
8062     const int repeat = count();
8063 
8064     // set anchor if not already set
8065     if (setupAnchor) {
8066         // Select nothing with 'inner' on empty line.
8067         if (inner && atEmptyLine() && repeat == 1) {
8068             g.movetype = MoveExclusive;
8069             return;
8070         }
8071         moveToBoundaryStart(1, simple, false);
8072         setAnchor();
8073     } else if (forward) {
8074         moveRight();
8075         if (atEndOfLine())
8076             moveRight();
8077     } else {
8078         moveLeft();
8079         if (atBlockStart())
8080             moveLeft();
8081     }
8082 
8083     if (inner) {
8084         moveToBoundaryEnd(repeat, simple);
8085     } else {
8086         const int direction = forward ? 1 : -1;
8087         for (int i = 0; i < repeat; ++i) {
8088             // select leading spaces
8089             bool leadingSpace = characterAtCursor().isSpace();
8090             if (leadingSpace) {
8091                 if (forward)
8092                     moveToNextBoundaryStart(1, simple);
8093                 else
8094                     moveToNextBoundaryEnd(1, simple, false);
8095             }
8096 
8097             // select word
8098             if (forward)
8099                 moveToWordEnd(1, simple);
8100             else
8101                 moveToWordStart(1, simple, false);
8102 
8103             // select trailing spaces if no leading space
8104             QChar afterCursor = document()->characterAt(position() + direction);
8105             if (!leadingSpace && afterCursor.isSpace() && afterCursor != ParagraphSeparator
8106                 && !atBlockStart()) {
8107                 if (forward)
8108                     moveToNextBoundaryEnd(1, simple);
8109                 else
8110                     moveToNextBoundaryStart(1, simple, false);
8111             }
8112 
8113             // if there are no trailing spaces in selection select all leading spaces
8114             // after previous character
8115             if (setupAnchor && (!characterAtCursor().isSpace() || atBlockEnd())) {
8116                 int min = block().position();
8117                 int pos = anchor();
8118                 while (pos >= min && document()->characterAt(--pos).isSpace()) {}
8119                 if (pos >= min)
8120                     setAnchorAndPosition(pos + 1, position());
8121             }
8122 
8123             if (i + 1 < repeat) {
8124                 if (forward) {
8125                     moveRight();
8126                     if (atEndOfLine())
8127                         moveRight();
8128                 } else {
8129                     moveLeft();
8130                     if (atBlockStart())
8131                         moveLeft();
8132                 }
8133             }
8134         }
8135     }
8136 
8137     if (inner) {
8138         g.movetype = MoveInclusive;
8139     } else {
8140         g.movetype = MoveExclusive;
8141         if (isNoVisualMode()) {
8142             moveRight();
8143             if (atEndOfLine())
8144                 moveRight();
8145         } else if (isVisualLineMode()) {
8146             g.visualMode = VisualCharMode;
8147         }
8148     }
8149 
8150     setTargetColumn();
8151 }
8152 
selectWordTextObject(bool inner)8153 void FakeVimHandler::Private::selectWordTextObject(bool inner)
8154 {
8155     selectTextObject(false, inner);
8156 }
8157 
selectWORDTextObject(bool inner)8158 void FakeVimHandler::Private::selectWORDTextObject(bool inner)
8159 {
8160     selectTextObject(true, inner);
8161 }
8162 
selectSentenceTextObject(bool inner)8163 void FakeVimHandler::Private::selectSentenceTextObject(bool inner)
8164 {
8165     Q_UNUSED(inner);
8166 }
8167 
selectParagraphTextObject(bool inner)8168 void FakeVimHandler::Private::selectParagraphTextObject(bool inner)
8169 {
8170     Q_UNUSED(inner);
8171 }
8172 
selectBlockTextObject(bool inner,char left,char right)8173 bool FakeVimHandler::Private::selectBlockTextObject(bool inner,
8174     char left, char right)
8175 {
8176     QString sleft = QString(QLatin1Char(left));
8177     QString sright = QString(QLatin1Char(right));
8178 
8179     int p1 = blockBoundary(sleft, sright, false, count());
8180     if (p1 == -1)
8181         return false;
8182 
8183     int p2 = blockBoundary(sleft, sright, true, count());
8184     if (p2 == -1)
8185         return false;
8186 
8187     if (inner)
8188         p1 += sleft.size();
8189     else
8190         p2 -= sright.size() - 2;
8191 
8192     if (isVisualMode())
8193         --p2;
8194 
8195     setAnchorAndPosition(p1, p2);
8196     g.movetype = MoveExclusive;
8197 
8198     return true;
8199 }
8200 
changeNumberTextObject(int count)8201 bool FakeVimHandler::Private::changeNumberTextObject(int count)
8202 {
8203     const QTextBlock block = this->block();
8204     const QString lineText = block.text();
8205     const int posMin = m_cursor.positionInBlock() + 1;
8206 
8207     // find first decimal, hexadecimal or octal number under or after cursor position
8208     QRegExp re(_("(0[xX])(0*[0-9a-fA-F]+)|(0)(0*[0-7]+)(?=\\D|$)|(\\d+)"));
8209     int pos = 0;
8210     while ((pos = re.indexIn(lineText, pos)) != -1 && pos + re.matchedLength() < posMin)
8211         ++pos;
8212     if (pos == -1)
8213         return false;
8214     int len = re.matchedLength();
8215     QString prefix = re.cap(1) + re.cap(3);
8216     bool hex = prefix.length() >= 2 && (prefix[1].toLower() == QLatin1Char('x'));
8217     bool octal = !hex && !prefix.isEmpty();
8218     const QString num = hex ? re.cap(2) : octal ? re.cap(4) : re.cap(5);
8219 
8220     // parse value
8221     bool ok;
8222     int base = hex ? 16 : octal ? 8 : 10;
8223     qlonglong value = 0;  // decimal value
8224     qlonglong uvalue = 0; // hexadecimal or octal value (only unsigned)
8225     if (hex || octal)
8226         uvalue = num.toULongLong(&ok, base);
8227     else
8228         value = num.toLongLong(&ok, base);
8229     QTC_ASSERT(ok, qDebug() << "Cannot parse number:" << num << "base:" << base; return false);
8230 
8231     // negative decimal number
8232     if (!octal && !hex && pos > 0 && lineText[pos - 1] == QLatin1Char('-')) {
8233         value = -value;
8234         --pos;
8235         ++len;
8236     }
8237 
8238     // result to string
8239     QString repl;
8240     if (hex || octal)
8241         repl = QString::number(uvalue + count, base);
8242     else
8243         repl = QString::number(value + count, base);
8244 
8245     // convert hexadecimal number to upper-case if last letter was upper-case
8246     if (hex) {
8247         const int lastLetter = num.lastIndexOf(QRegExp(_("[a-fA-F]")));
8248         if (lastLetter != -1 && num[lastLetter].isUpper())
8249             repl = repl.toUpper();
8250     }
8251 
8252     // preserve leading zeroes
8253     if ((octal || hex) && repl.size() < num.size())
8254         prefix.append(QString::fromLatin1("0").repeated(num.size() - repl.size()));
8255     repl.prepend(prefix);
8256 
8257     pos += block.position();
8258     pushUndoState();
8259     setAnchorAndPosition(pos, pos + len);
8260     replaceText(currentRange(), repl);
8261     setPosition(pos + repl.size() - 1);
8262 
8263     return true;
8264 }
8265 
selectQuotedStringTextObject(bool inner,const QString & quote)8266 bool FakeVimHandler::Private::selectQuotedStringTextObject(bool inner,
8267     const QString &quote)
8268 {
8269     QTextCursor tc = m_cursor;
8270     int sz = quote.size();
8271 
8272     QTextCursor tc1;
8273     QTextCursor tc2(document());
8274     while (tc2 <= tc) {
8275         tc1 = document()->find(quote, tc2);
8276         if (tc1.isNull())
8277             return false;
8278         tc2 = document()->find(quote, tc1);
8279         if (tc2.isNull())
8280             return false;
8281     }
8282 
8283     int p1 = tc1.position();
8284     int p2 = tc2.position();
8285     if (inner) {
8286         p2 = qMax(p1, p2 - sz);
8287         if (document()->characterAt(p1) == ParagraphSeparator)
8288             ++p1;
8289     } else {
8290         p1 -= sz;
8291         p2 -= sz - 1;
8292     }
8293 
8294     if (isVisualMode())
8295         --p2;
8296 
8297     setAnchorAndPosition(p1, p2);
8298     g.movetype = MoveExclusive;
8299 
8300     return true;
8301 }
8302 
mark(QChar code) const8303 Mark FakeVimHandler::Private::mark(QChar code) const
8304 {
8305     if (isVisualMode()) {
8306         if (code == QLatin1Char('<'))
8307             return CursorPosition(document(), qMin(anchor(), position()));
8308         if (code == QLatin1Char('>'))
8309             return CursorPosition(document(), qMax(anchor(), position()));
8310     }
8311     if (code == QLatin1Char('.'))
8312         return m_buffer->lastChangePosition;
8313     if (code.isUpper())
8314         return g.marks.value(code);
8315 
8316     return m_buffer->marks.value(code);
8317 }
8318 
setMark(QChar code,CursorPosition position)8319 void FakeVimHandler::Private::setMark(QChar code, CursorPosition position)
8320 {
8321     if (code.isUpper())
8322         g.marks[code] = Mark(position, m_currentFileName);
8323     else
8324         m_buffer->marks[code] = Mark(position);
8325 }
8326 
jumpToMark(QChar mark,bool backTickMode)8327 bool FakeVimHandler::Private::jumpToMark(QChar mark, bool backTickMode)
8328 {
8329     Mark m = this->mark(mark);
8330     if (!m.isValid()) {
8331         showMessage(MessageError, msgMarkNotSet(mark));
8332         return false;
8333     }
8334     if (!m.isLocal(m_currentFileName)) {
8335         emit q->jumpToGlobalMark(mark, backTickMode, m.fileName());
8336         return false;
8337     }
8338 
8339     if ((mark == QLatin1Char('\'') || mark == QLatin1Char('`')) && !m_buffer->jumpListUndo.isEmpty())
8340         m_buffer->jumpListUndo.pop();
8341     recordJump();
8342     setCursorPosition(m.position(document()));
8343     if (!backTickMode)
8344         moveToFirstNonBlankOnLine();
8345     if (g.submode == NoSubMode)
8346         setAnchor();
8347     setTargetColumn();
8348 
8349     return true;
8350 }
8351 
updateMarks(const Marks & newMarks)8352 void FakeVimHandler::Private::updateMarks(const Marks &newMarks)
8353 {
8354     for (MarksIterator it(newMarks); it.hasNext(); ) {
8355         it.next();
8356         m_buffer->marks[it.key()] = it.value();
8357     }
8358 }
8359 
registerRangeMode(int reg) const8360 RangeMode FakeVimHandler::Private::registerRangeMode(int reg) const
8361 {
8362     bool isClipboard;
8363     bool isSelection;
8364     getRegisterType(reg, &isClipboard, &isSelection);
8365 
8366     if (isClipboard || isSelection) {
8367         QClipboard *clipboard = QApplication::clipboard();
8368         QClipboard::Mode mode = isClipboard ? QClipboard::Clipboard : QClipboard::Selection;
8369 
8370         // Use range mode from Vim's clipboard data if available.
8371         const QMimeData *data = clipboard->mimeData(mode);
8372         if (data && data->hasFormat(vimMimeText)) {
8373             QByteArray bytes = data->data(vimMimeText);
8374             if (bytes.length() > 0)
8375                 return static_cast<RangeMode>(bytes.at(0));
8376         }
8377 
8378         // If register content is clipboard:
8379         //  - return RangeLineMode if text ends with new line char,
8380         //  - return RangeCharMode otherwise.
8381         QString text = clipboard->text(mode);
8382         return (text.endsWith(QLatin1Char('\n')) || text.endsWith(QLatin1Char('\r'))) ? RangeLineMode : RangeCharMode;
8383     }
8384 
8385     return g.registers[reg].rangemode;
8386 }
8387 
setRegister(int reg,const QString & contents,RangeMode mode)8388 void FakeVimHandler::Private::setRegister(int reg, const QString &contents, RangeMode mode)
8389 {
8390     bool copyToClipboard;
8391     bool copyToSelection;
8392     getRegisterType(reg, &copyToClipboard, &copyToSelection);
8393 
8394     QString contents2 = contents;
8395     if (mode == RangeLineMode && !contents2.endsWith(QLatin1Char('\n')))
8396         contents2.append(QLatin1Char('\n'));
8397 
8398     if (copyToClipboard || copyToSelection) {
8399         if (copyToClipboard)
8400             setClipboardData(contents2, mode, QClipboard::Clipboard);
8401         if (copyToSelection)
8402             setClipboardData(contents2, mode, QClipboard::Selection);
8403     } else {
8404         g.registers[reg].contents = contents2;
8405         g.registers[reg].rangemode = mode;
8406     }
8407 }
8408 
registerContents(int reg) const8409 QString FakeVimHandler::Private::registerContents(int reg) const
8410 {
8411     bool copyFromClipboard;
8412     bool copyFromSelection;
8413     getRegisterType(reg, &copyFromClipboard, &copyFromSelection);
8414 
8415     if (copyFromClipboard || copyFromSelection) {
8416         QClipboard *clipboard = QApplication::clipboard();
8417         if (copyFromClipboard)
8418             return clipboard->text(QClipboard::Clipboard);
8419         if (copyFromSelection)
8420             return clipboard->text(QClipboard::Selection);
8421     }
8422 
8423     return g.registers[reg].contents;
8424 }
8425 
getRegisterType(int reg,bool * isClipboard,bool * isSelection) const8426 void FakeVimHandler::Private::getRegisterType(int reg, bool *isClipboard, bool *isSelection) const
8427 {
8428     bool clipboard = false;
8429     bool selection = false;
8430 
8431     if (reg == QLatin1Char('"')) {
8432         QStringList list = config(ConfigClipboard).toString().split(QLatin1Char(','));
8433         clipboard = list.contains(_("unnamedplus"));
8434         selection = list.contains(_("unnamed"));
8435     } else if (reg == QLatin1Char('+')) {
8436         clipboard = true;
8437     } else if (reg == QLatin1Char('*')) {
8438         selection = true;
8439     }
8440 
8441     // selection (primary) is clipboard on systems without selection support
8442     if (selection && !QApplication::clipboard()->supportsSelection()) {
8443         clipboard = true;
8444         selection = false;
8445     }
8446 
8447     if (isClipboard != 0)
8448         *isClipboard = clipboard;
8449     if (isSelection != 0)
8450         *isSelection = selection;
8451 }
8452 
8453 ///////////////////////////////////////////////////////////////////////
8454 //
8455 // FakeVimHandler
8456 //
8457 ///////////////////////////////////////////////////////////////////////
8458 
FakeVimHandler(QWidget * widget,QObject * parent)8459 FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
8460     : QObject(parent), d(new Private(this, widget))
8461 {}
8462 
~FakeVimHandler()8463 FakeVimHandler::~FakeVimHandler()
8464 {
8465     delete d;
8466 }
8467 
8468 // gracefully handle that the parent editor is deleted
disconnectFromEditor()8469 void FakeVimHandler::disconnectFromEditor()
8470 {
8471     d->m_textedit = 0;
8472     d->m_plaintextedit = 0;
8473 }
8474 
eventFilter(QObject * ob,QEvent * ev)8475 bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
8476 {
8477 #ifndef FAKEVIM_STANDALONE
8478     if (!theFakeVimSetting(ConfigUseFakeVim)->value().toBool())
8479         return QObject::eventFilter(ob, ev);
8480 #endif
8481 
8482     // Catch mouse events on the viewport.
8483     QWidget *viewport = 0;
8484     if (d->m_plaintextedit)
8485         viewport = d->m_plaintextedit->viewport();
8486     else if (d->m_textedit)
8487         viewport = d->m_textedit->viewport();
8488     if (ob == viewport) {
8489         if (ev->type() == QEvent::MouseButtonRelease) {
8490             QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
8491             if (mev->button() == Qt::LeftButton) {
8492                 d->importSelection();
8493                 //return true;
8494             }
8495         }
8496         if (ev->type() == QEvent::MouseButtonPress) {
8497             QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
8498             if (mev->button() == Qt::LeftButton)
8499                 Private::g.visualMode = NoVisualMode;
8500         }
8501         return QObject::eventFilter(ob, ev);
8502     }
8503 
8504     if (ev->type() == QEvent::Shortcut) {
8505         d->passShortcuts(false);
8506         return false;
8507     }
8508 
8509     if (ev->type() == QEvent::InputMethod && ob == d->editor()) {
8510         // This handles simple dead keys. The sequence of events is
8511         // KeyRelease-InputMethod-KeyRelease  for dead keys instead of
8512         // KeyPress-KeyRelease as for simple keys. As vi acts on key presses,
8513         // we have to act on the InputMethod event.
8514         // FIXME: A first approximation working for e.g. ^ on a German keyboard
8515         QInputMethodEvent *imev = static_cast<QInputMethodEvent *>(ev);
8516         KEY_DEBUG("INPUTMETHOD" << imev->commitString() << imev->preeditString());
8517         QString commitString = imev->commitString();
8518         int key = commitString.size() == 1 ? commitString.at(0).unicode() : 0;
8519         QKeyEvent kev(QEvent::KeyPress, key, Qt::KeyboardModifiers(), commitString);
8520         EventResult res = d->handleEvent(&kev);
8521         return res == EventHandled || res == EventCancelled;
8522     }
8523 
8524     if (ev->type() == QEvent::KeyPress &&
8525         (ob == d->editor()
8526          || (Private::g.mode == ExMode || Private::g.subsubmode == SearchSubSubMode))) {
8527         QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
8528         KEY_DEBUG("KEYPRESS" << kev->key() << kev->text() << QChar(kev->key()));
8529         EventResult res = d->handleEvent(kev);
8530         //if (Private::g.mode == InsertMode)
8531         //    emit completionRequested();
8532         // returning false core the app see it
8533         //KEY_DEBUG("HANDLED CODE:" << res);
8534         //return res != EventPassedToCore;
8535         //return true;
8536         return res == EventHandled || res == EventCancelled;
8537     }
8538 
8539     if (ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
8540         QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
8541         if (d->wantsOverride(kev)) {
8542             KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
8543             ev->accept(); // accepting means "don't run the shortcuts"
8544             return true;
8545         }
8546         KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
8547         return true;
8548     }
8549 
8550     if (ev->type() == QEvent::FocusIn && ob == d->editor())
8551         d->focus();
8552 
8553     return QObject::eventFilter(ob, ev);
8554 }
8555 
installEventFilter()8556 void FakeVimHandler::installEventFilter()
8557 {
8558     d->installEventFilter();
8559 }
8560 
setupWidget()8561 void FakeVimHandler::setupWidget()
8562 {
8563     d->setupWidget();
8564 }
8565 
restoreWidget(int tabSize)8566 void FakeVimHandler::restoreWidget(int tabSize)
8567 {
8568     d->restoreWidget(tabSize);
8569 }
8570 
handleCommand(const QString & cmd)8571 void FakeVimHandler::handleCommand(const QString &cmd)
8572 {
8573     d->enterFakeVim();
8574     d->handleCommand(cmd);
8575     d->leaveFakeVim();
8576 }
8577 
handleReplay(const QString & keys)8578 void FakeVimHandler::handleReplay(const QString &keys)
8579 {
8580     d->enterFakeVim();
8581     d->replay(keys);
8582     d->leaveFakeVim();
8583 }
8584 
handleInput(const QString & keys)8585 void FakeVimHandler::handleInput(const QString &keys)
8586 {
8587     Inputs inputs(keys);
8588     d->enterFakeVim();
8589     foreach (const Input &input, inputs)
8590         d->handleKey(input);
8591     d->leaveFakeVim();
8592 }
8593 
setCurrentFileName(const QString & fileName)8594 void FakeVimHandler::setCurrentFileName(const QString &fileName)
8595 {
8596     d->m_currentFileName = fileName;
8597 }
8598 
currentFileName() const8599 QString FakeVimHandler::currentFileName() const
8600 {
8601     return d->m_currentFileName;
8602 }
8603 
showMessage(MessageLevel level,const QString & msg)8604 void FakeVimHandler::showMessage(MessageLevel level, const QString &msg)
8605 {
8606     d->showMessage(level, msg);
8607 }
8608 
widget()8609 QWidget *FakeVimHandler::widget()
8610 {
8611     return d->editor();
8612 }
8613 
8614 // Test only
physicalIndentation(const QString & line) const8615 int FakeVimHandler::physicalIndentation(const QString &line) const
8616 {
8617     Column ind = d->indentation(line);
8618     return ind.physical;
8619 }
8620 
logicalIndentation(const QString & line) const8621 int FakeVimHandler::logicalIndentation(const QString &line) const
8622 {
8623     Column ind = d->indentation(line);
8624     return ind.logical;
8625 }
8626 
tabExpand(int n) const8627 QString FakeVimHandler::tabExpand(int n) const
8628 {
8629     return d->tabExpand(n);
8630 }
8631 
miniBufferTextEdited(const QString & text,int cursorPos,int anchorPos)8632 void FakeVimHandler::miniBufferTextEdited(const QString &text, int cursorPos, int anchorPos)
8633 {
8634     d->miniBufferTextEdited(text, cursorPos, anchorPos);
8635 }
8636 
setTextCursorPosition(int position)8637 void FakeVimHandler::setTextCursorPosition(int position)
8638 {
8639     int pos = qMax(0, qMin(position, d->lastPositionInDocument()));
8640     if (d->isVisualMode())
8641         d->setPosition(pos);
8642     else
8643         d->setAnchorAndPosition(pos, pos);
8644     d->m_fakeEnd = false;
8645     d->setTargetColumn();
8646 
8647     if (!d->m_inFakeVim)
8648         d->commitCursor();
8649 }
8650 
textCursor() const8651 QTextCursor FakeVimHandler::textCursor() const
8652 {
8653     return d->m_cursor;
8654 }
8655 
setTextCursor(const QTextCursor & cursor)8656 void FakeVimHandler::setTextCursor(const QTextCursor &cursor)
8657 {
8658     d->m_cursor = cursor;
8659 }
8660 
jumpToLocalMark(QChar mark,bool backTickMode)8661 bool FakeVimHandler::jumpToLocalMark(QChar mark, bool backTickMode)
8662 {
8663     return d->jumpToMark(mark, backTickMode);
8664 }
8665 
8666 } // namespace Internal
8667 } // namespace FakeVim
8668 
8669 Q_DECLARE_METATYPE(FakeVim::Internal::FakeVimHandler::Private::BufferDataPtr)
8670 
8671 #include "fakevimhandler.moc"
8672