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