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