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