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