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