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 ®)
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 ¤t() 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 ¤tInputs() 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 "e);
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 ®);
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 ®)
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 "e)
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(®, &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(®, ©ToClipboard, ©ToSelection, &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(®, ©FromClipboard, ©FromSelection);
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