PageRenderTime 96ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 2ms

/src/plugins/fakevim/fakevimhandler.cpp

https://bitbucket.org/kyanha/qt-creator
C++ | 7466 lines | 6667 code | 466 blank | 333 comment | 1336 complexity | f5f9888d50ce84b29206b229987fc20a MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2012 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. // ATTENTION:
  31. //
  32. // 1 Please do not add any direct dependencies to other Qt Creator code here.
  33. // Instead emit signals and let the FakeVimPlugin channel the information to
  34. // Qt Creator. The idea is to keep this file here in a "clean" state that
  35. // allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
  36. //
  37. // 2 There are a few auto tests located in ../../../tests/auto/fakevim.
  38. // Commands that are covered there are marked as "// tested" below.
  39. //
  40. // 3 Some conventions:
  41. //
  42. // Use 1 based line numbers and 0 based column numbers. Even though
  43. // the 1 based line are not nice it matches vim's and QTextEdit's 'line'
  44. // concepts.
  45. //
  46. // Do not pass QTextCursor etc around unless really needed. Convert
  47. // early to line/column.
  48. //
  49. // A QTextCursor is always between characters, whereas vi's cursor is always
  50. // over a character. FakeVim interprets the QTextCursor to be over the character
  51. // to the right of the QTextCursor's position().
  52. //
  53. // A current "region of interest"
  54. // spans between anchor(), (i.e. the character below anchor()), and
  55. // position(). The character below position() is not included
  56. // if the last movement command was exclusive (MoveExclusive).
  57. //
  58. #include "fakevimhandler.h"
  59. #include <utils/hostosinfo.h>
  60. #include <utils/qtcassert.h>
  61. #include <QDebug>
  62. #include <QFile>
  63. #include <QObject>
  64. #include <QPointer>
  65. #include <QProcess>
  66. #include <QRegExp>
  67. #include <QTextStream>
  68. #include <QTimer>
  69. #include <QtAlgorithms>
  70. #include <QStack>
  71. #include <QApplication>
  72. #include <QClipboard>
  73. #include <QInputMethodEvent>
  74. #include <QKeyEvent>
  75. #include <QLineEdit>
  76. #include <QPlainTextEdit>
  77. #include <QScrollBar>
  78. #include <QTextBlock>
  79. #include <QTextCursor>
  80. #include <QTextDocumentFragment>
  81. #include <QTextEdit>
  82. #include <QMimeData>
  83. #include <algorithm>
  84. #include <climits>
  85. #include <ctype.h>
  86. //#define DEBUG_KEY 1
  87. #if DEBUG_KEY
  88. # define KEY_DEBUG(s) qDebug() << s
  89. #else
  90. # define KEY_DEBUG(s)
  91. #endif
  92. //#define DEBUG_UNDO 1
  93. #if DEBUG_UNDO
  94. # define UNDO_DEBUG(s) qDebug() << << revision() << s
  95. #else
  96. # define UNDO_DEBUG(s)
  97. #endif
  98. using namespace Utils;
  99. namespace FakeVim {
  100. namespace Internal {
  101. ///////////////////////////////////////////////////////////////////////
  102. //
  103. // FakeVimHandler
  104. //
  105. ///////////////////////////////////////////////////////////////////////
  106. #define StartOfLine QTextCursor::StartOfLine
  107. #define EndOfLine QTextCursor::EndOfLine
  108. #define MoveAnchor QTextCursor::MoveAnchor
  109. #define KeepAnchor QTextCursor::KeepAnchor
  110. #define Up QTextCursor::Up
  111. #define Down QTextCursor::Down
  112. #define Right QTextCursor::Right
  113. #define Left QTextCursor::Left
  114. #define EndOfDocument QTextCursor::End
  115. #define StartOfDocument QTextCursor::Start
  116. #define ParagraphSeparator QChar::ParagraphSeparator
  117. #define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
  118. #define MetaModifier // Use HostOsInfo::controlModifier() instead
  119. #define ControlModifier // Use HostOsInfo::controlModifier() instead
  120. typedef QLatin1String _;
  121. /* Clipboard MIME types used by Vim. */
  122. static const QString vimMimeText = _("_VIM_TEXT");
  123. static const QString vimMimeTextEncoded = _("_VIMENC_TEXT");
  124. using namespace Qt;
  125. /*! A \e Mode represents one of the basic modes of operation of FakeVim.
  126. */
  127. enum Mode
  128. {
  129. InsertMode,
  130. ReplaceMode,
  131. CommandMode,
  132. ExMode
  133. };
  134. /*! A \e SubMode is used for things that require one more data item
  135. and are 'nested' behind a \l Mode.
  136. */
  137. enum SubMode
  138. {
  139. NoSubMode,
  140. ChangeSubMode, // Used for c
  141. DeleteSubMode, // Used for d
  142. FilterSubMode, // Used for !
  143. IndentSubMode, // Used for =
  144. RegisterSubMode, // Used for "
  145. ShiftLeftSubMode, // Used for <
  146. ShiftRightSubMode, // Used for >
  147. InvertCaseSubMode, // Used for g~
  148. DownCaseSubMode, // Used for gu
  149. UpCaseSubMode, // Used for gU
  150. WindowSubMode, // Used for Ctrl-w
  151. YankSubMode, // Used for y
  152. ZSubMode, // Used for z
  153. CapitalZSubMode, // Used for Z
  154. ReplaceSubMode // Used for r
  155. };
  156. /*! A \e SubSubMode is used for things that require one more data item
  157. and are 'nested' behind a \l SubMode.
  158. */
  159. enum SubSubMode
  160. {
  161. NoSubSubMode,
  162. FtSubSubMode, // Used for f, F, t, T.
  163. MarkSubSubMode, // Used for m.
  164. BackTickSubSubMode, // Used for `.
  165. TickSubSubMode, // Used for '.
  166. TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
  167. ZSubSubMode, // Used for zj, zk
  168. OpenSquareSubSubMode, // Used for [{, {(, [z
  169. CloseSquareSubSubMode, // Used for ]}, ]), ]z
  170. SearchSubSubMode
  171. };
  172. enum VisualMode
  173. {
  174. NoVisualMode,
  175. VisualCharMode,
  176. VisualLineMode,
  177. VisualBlockMode
  178. };
  179. enum MoveType
  180. {
  181. MoveExclusive,
  182. MoveInclusive,
  183. MoveLineWise
  184. };
  185. /*!
  186. \enum RangeMode
  187. The \e RangeMode serves as a means to define how the "Range" between
  188. the \l cursor and the \l anchor position is to be interpreted.
  189. \value RangeCharMode Entered by pressing \key v. The range includes
  190. all characters between cursor and anchor.
  191. \value RangeLineMode Entered by pressing \key V. The range includes
  192. all lines between the line of the cursor and
  193. the line of the anchor.
  194. \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
  195. newline when deleting.
  196. \value RangeBlockMode Entered by pressing \key Ctrl-v. The range includes
  197. all characters with line and column coordinates
  198. between line and columns coordinates of cursor and
  199. anchor.
  200. \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
  201. all characters in the affected lines up to the end
  202. of these lines.
  203. */
  204. enum EventResult
  205. {
  206. EventHandled,
  207. EventUnhandled,
  208. EventCancelled, // Event is handled but a sub mode was cancelled.
  209. EventPassedToCore
  210. };
  211. struct CursorPosition
  212. {
  213. CursorPosition() : line(-1), column(-1) {}
  214. CursorPosition(int block, int column) : line(block), column(column) {}
  215. explicit CursorPosition(const QTextCursor &tc)
  216. : line(tc.block().blockNumber()), column(tc.positionInBlock()) {}
  217. CursorPosition(const QTextDocument *document, int position)
  218. {
  219. QTextBlock block = document->findBlock(position);
  220. line = block.blockNumber();
  221. column = position - block.position();
  222. }
  223. bool isValid() const { return line >= 0 && column >= 0; }
  224. bool operator>(const CursorPosition &other) const
  225. { return line > other.line || column > other.column; }
  226. bool operator==(const CursorPosition &other) const
  227. { return line == other.line && column == other.column; }
  228. bool operator!=(const CursorPosition &other) const { return !operator==(other); }
  229. int line; // Line in document (from 0, folded lines included).
  230. int column; // Position on line.
  231. };
  232. struct Mark
  233. {
  234. Mark(const CursorPosition &pos = CursorPosition(), const QString &fileName = QString())
  235. : position(pos), fileName(fileName) {}
  236. bool isValid() const { return position.isValid(); }
  237. bool isLocal(const QString &localFileName) const
  238. {
  239. return fileName.isEmpty() || fileName == localFileName;
  240. }
  241. CursorPosition position;
  242. QString fileName;
  243. };
  244. typedef QHash<QChar, Mark> Marks;
  245. typedef QHashIterator<QChar, Mark> MarksIterator;
  246. struct State
  247. {
  248. State() : revision(-1), position(), marks(), lastVisualMode(NoVisualMode),
  249. lastVisualModeInverted(false) {}
  250. State(int revision, const CursorPosition &position, const Marks &marks,
  251. VisualMode lastVisualMode, bool lastVisualModeInverted) : revision(revision),
  252. position(position), marks(marks), lastVisualMode(lastVisualMode),
  253. lastVisualModeInverted(lastVisualModeInverted) {}
  254. int revision;
  255. CursorPosition position;
  256. Marks marks;
  257. VisualMode lastVisualMode;
  258. bool lastVisualModeInverted;
  259. };
  260. struct Column
  261. {
  262. Column(int p, int l) : physical(p), logical(l) {}
  263. int physical; // Number of characters in the data.
  264. int logical; // Column on screen.
  265. };
  266. QDebug operator<<(QDebug ts, const Column &col)
  267. {
  268. return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
  269. }
  270. struct Register
  271. {
  272. Register() : rangemode(RangeCharMode) {}
  273. Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
  274. Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
  275. QString contents;
  276. RangeMode rangemode;
  277. };
  278. QDebug operator<<(QDebug ts, const Register &reg)
  279. {
  280. return ts << reg.contents;
  281. }
  282. struct SearchData
  283. {
  284. SearchData()
  285. {
  286. forward = true;
  287. highlightMatches = true;
  288. }
  289. QString needle;
  290. bool forward;
  291. bool highlightMatches;
  292. };
  293. // If string begins with given prefix remove it with trailing spaces and return true.
  294. static bool eatString(const char *prefix, QString *str)
  295. {
  296. if (!str->startsWith(_(prefix)))
  297. return false;
  298. *str = str->mid(strlen(prefix)).trimmed();
  299. return true;
  300. }
  301. static QRegExp vimPatternToQtPattern(QString needle, bool smartcase)
  302. {
  303. /* Transformations (Vim regexp -> QRegExp):
  304. * \a -> [A-Za-z]
  305. * \A -> [^A-Za-z]
  306. * \h -> [A-Za-z_]
  307. * \H -> [^A-Za-z_]
  308. * \l -> [a-z]
  309. * \L -> [^a-z]
  310. * \o -> [0-7]
  311. * \O -> [^0-7]
  312. * \u -> [A-Z]
  313. * \U -> [^A-Z]
  314. * \x -> [0-9A-Fa-f]
  315. * \X -> [^0-9A-Fa-f]
  316. *
  317. * \< -> \b
  318. * \> -> \b
  319. * [] -> \[\]
  320. * \= -> ?
  321. *
  322. * (...) <-> \(...\)
  323. * {...} <-> \{...\}
  324. * | <-> \|
  325. * ? <-> \?
  326. * + <-> \+
  327. * \{...} -> {...}
  328. *
  329. * \c - set ignorecase for rest
  330. * \C - set noignorecase for rest
  331. */
  332. bool ignorecase = smartcase && !needle.contains(QRegExp(_("[A-Z]")));
  333. QString pattern;
  334. pattern.reserve(2 * needle.size());
  335. bool escape = false;
  336. bool brace = false;
  337. bool embraced = false;
  338. bool range = false;
  339. bool curly = false;
  340. foreach (const QChar &c, needle) {
  341. if (brace) {
  342. brace = false;
  343. if (c == QLatin1Char(']')) {
  344. pattern.append(_("\\[\\]"));
  345. continue;
  346. }
  347. pattern.append(QLatin1Char('['));
  348. escape = true;
  349. embraced = true;
  350. }
  351. if (embraced) {
  352. if (range) {
  353. QChar c2 = pattern[pattern.size() - 2];
  354. pattern.remove(pattern.size() - 2, 2);
  355. pattern.append(c2.toUpper() + QLatin1Char('-') + c.toUpper());
  356. pattern.append(c2.toLower() + QLatin1Char('-') + c.toLower());
  357. range = false;
  358. } else if (escape) {
  359. escape = false;
  360. pattern.append(c);
  361. } else if (c == QLatin1Char('\\')) {
  362. escape = true;
  363. } else if (c == QLatin1Char(']')) {
  364. pattern.append(QLatin1Char(']'));
  365. embraced = false;
  366. } else if (c == QLatin1Char('-')) {
  367. range = ignorecase && pattern[pattern.size() - 1].isLetter();
  368. pattern.append(QLatin1Char('-'));
  369. } else if (c.isLetter() && ignorecase) {
  370. pattern.append(c.toLower()).append(c.toUpper());
  371. } else {
  372. pattern.append(c);
  373. }
  374. } else if (QString::fromLatin1("(){}+|?").indexOf(c) != -1) {
  375. if (c == QLatin1Char('{')) {
  376. curly = escape;
  377. } else if (c == QLatin1Char('}') && curly) {
  378. curly = false;
  379. escape = true;
  380. }
  381. if (escape)
  382. escape = false;
  383. else
  384. pattern.append(QLatin1Char('\\'));
  385. pattern.append(c);
  386. } else if (escape) {
  387. // escape expression
  388. escape = false;
  389. if (c == QLatin1Char('<') || c == QLatin1Char('>'))
  390. pattern.append(_("\\b"));
  391. else if (c == QLatin1Char('a'))
  392. pattern.append(_("[a-zA-Z]"));
  393. else if (c == QLatin1Char('A'))
  394. pattern.append(_("[^a-zA-Z]"));
  395. else if (c == QLatin1Char('h'))
  396. pattern.append(_("[A-Za-z_]"));
  397. else if (c == QLatin1Char('H'))
  398. pattern.append(_("[^A-Za-z_]"));
  399. else if (c == QLatin1Char('c') || c == QLatin1Char('C'))
  400. ignorecase = (c == QLatin1Char('c'));
  401. else if (c == QLatin1Char('l'))
  402. pattern.append(_("[a-z]"));
  403. else if (c == QLatin1Char('L'))
  404. pattern.append(_("[^a-z]"));
  405. else if (c == QLatin1Char('o'))
  406. pattern.append(_("[0-7]"));
  407. else if (c == QLatin1Char('O'))
  408. pattern.append(_("[^0-7]"));
  409. else if (c == QLatin1Char('u'))
  410. pattern.append(_("[A-Z]"));
  411. else if (c == QLatin1Char('U'))
  412. pattern.append(_("[^A-Z]"));
  413. else if (c == QLatin1Char('x'))
  414. pattern.append(_("[0-9A-Fa-f]"));
  415. else if (c == QLatin1Char('X'))
  416. pattern.append(_("[^0-9A-Fa-f]"));
  417. else if (c == QLatin1Char('='))
  418. pattern.append(_("?"));
  419. else
  420. pattern.append(QLatin1Char('\\') + c);
  421. } else {
  422. // unescaped expression
  423. if (c == QLatin1Char('\\'))
  424. escape = true;
  425. else if (c == QLatin1Char('['))
  426. brace = true;
  427. else if (c.isLetter() && ignorecase)
  428. pattern.append(QLatin1Char('[') + c.toLower() + c.toUpper() + QLatin1Char(']'));
  429. else
  430. pattern.append(c);
  431. }
  432. }
  433. if (escape)
  434. pattern.append(QLatin1Char('\\'));
  435. else if (brace)
  436. pattern.append(QLatin1Char('['));
  437. return QRegExp(pattern);
  438. }
  439. static bool afterEndOfLine(const QTextDocument *doc, int position)
  440. {
  441. return doc->characterAt(position) == ParagraphSeparator
  442. && doc->findBlock(position).length() > 1;
  443. }
  444. static void searchForward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
  445. {
  446. const QTextDocument *doc = tc->document();
  447. const int startPos = tc->position();
  448. // Search from beginning of line so that matched text is the same.
  449. tc->movePosition(StartOfLine);
  450. // forward to current position
  451. *tc = doc->find(needleExp, *tc);
  452. while (!tc->isNull() && tc->anchor() < startPos) {
  453. if (!tc->hasSelection())
  454. tc->movePosition(Right);
  455. if (tc->atBlockEnd())
  456. tc->movePosition(Right);
  457. *tc = doc->find(needleExp, *tc);
  458. }
  459. if (tc->isNull())
  460. return;
  461. --*repeat;
  462. while (*repeat > 0) {
  463. if (!tc->hasSelection())
  464. tc->movePosition(Right);
  465. if (tc->atBlockEnd())
  466. tc->movePosition(Right);
  467. *tc = doc->find(needleExp, *tc);
  468. if (tc->isNull())
  469. return;
  470. --*repeat;
  471. }
  472. if (!tc->isNull() && afterEndOfLine(doc, tc->anchor()))
  473. tc->movePosition(Left);
  474. }
  475. static void searchBackward(QTextCursor *tc, QRegExp &needleExp, int *repeat)
  476. {
  477. // Search from beginning of line so that matched text is the same.
  478. QTextBlock block = tc->block();
  479. QString line = block.text();
  480. int i = line.indexOf(needleExp, 0);
  481. while (i != -1 && i < tc->positionInBlock()) {
  482. --*repeat;
  483. i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
  484. if (i == line.size())
  485. i = -1;
  486. }
  487. if (i == tc->positionInBlock())
  488. --*repeat;
  489. while (*repeat > 0) {
  490. block = block.previous();
  491. if (!block.isValid())
  492. break;
  493. line = block.text();
  494. i = line.indexOf(needleExp, 0);
  495. while (i != -1) {
  496. --*repeat;
  497. i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
  498. if (i == line.size())
  499. i = -1;
  500. }
  501. }
  502. if (!block.isValid()) {
  503. *tc = QTextCursor();
  504. return;
  505. }
  506. i = line.indexOf(needleExp, 0);
  507. while (*repeat < 0) {
  508. i = line.indexOf(needleExp, i + qMax(1, needleExp.matchedLength()));
  509. ++*repeat;
  510. }
  511. tc->setPosition(block.position() + i);
  512. }
  513. static bool substituteText(QString *text, QRegExp &pattern, const QString &replacement,
  514. bool global)
  515. {
  516. bool substituted = false;
  517. int pos = 0;
  518. while (true) {
  519. pos = pattern.indexIn(*text, pos, QRegExp::CaretAtZero);
  520. if (pos == -1)
  521. break;
  522. substituted = true;
  523. QString matched = text->mid(pos, pattern.cap(0).size());
  524. QString repl;
  525. bool escape = false;
  526. // insert captured texts
  527. for (int i = 0; i < replacement.size(); ++i) {
  528. const QChar &c = replacement[i];
  529. if (escape) {
  530. escape = false;
  531. if (c.isDigit()) {
  532. if (c.digitValue() <= pattern.captureCount())
  533. repl += pattern.cap(c.digitValue());
  534. } else {
  535. repl += c;
  536. }
  537. } else {
  538. if (c == QLatin1Char('\\'))
  539. escape = true;
  540. else if (c == QLatin1Char('&'))
  541. repl += pattern.cap(0);
  542. else
  543. repl += c;
  544. }
  545. }
  546. text->replace(pos, matched.size(), repl);
  547. pos += qMax(1, repl.size());
  548. if (pos >= text->size() || !global)
  549. break;
  550. }
  551. return substituted;
  552. }
  553. static int findUnescaped(QChar c, const QString &line, int from)
  554. {
  555. for (int i = from; i < line.size(); ++i) {
  556. if (line.at(i) == c && (i == 0 || line.at(i - 1) != QLatin1Char('\\')))
  557. return i;
  558. }
  559. return -1;
  560. }
  561. static void setClipboardData(const QString &content, RangeMode mode,
  562. QClipboard::Mode clipboardMode)
  563. {
  564. QClipboard *clipboard = QApplication::clipboard();
  565. char vimRangeMode = mode;
  566. QByteArray bytes1;
  567. bytes1.append(vimRangeMode);
  568. bytes1.append(content.toUtf8());
  569. QByteArray bytes2;
  570. bytes2.append(vimRangeMode);
  571. bytes2.append("utf-8");
  572. bytes2.append('\0');
  573. bytes2.append(content.toUtf8());
  574. QMimeData *data = new QMimeData;
  575. data->setText(content);
  576. data->setData(vimMimeText, bytes1);
  577. data->setData(vimMimeTextEncoded, bytes2);
  578. clipboard->setMimeData(data, clipboardMode);
  579. }
  580. static const QMap<QString, int> &vimKeyNames()
  581. {
  582. static QMap<QString, int> k;
  583. if (!k.isEmpty())
  584. return k;
  585. // FIXME: Should be value of mapleader.
  586. k.insert(_("LEADER"), Key_Backslash);
  587. k.insert(_("SPACE"), Key_Space);
  588. k.insert(_("TAB"), Key_Tab);
  589. k.insert(_("NL"), Key_Return);
  590. k.insert(_("NEWLINE"), Key_Return);
  591. k.insert(_("LINEFEED"), Key_Return);
  592. k.insert(_("LF"), Key_Return);
  593. k.insert(_("CR"), Key_Return);
  594. k.insert(_("RETURN"), Key_Return);
  595. k.insert(_("ENTER"), Key_Return);
  596. k.insert(_("BS"), Key_Backspace);
  597. k.insert(_("BACKSPACE"), Key_Backspace);
  598. k.insert(_("ESC"), Key_Escape);
  599. k.insert(_("BAR"), Key_Bar);
  600. k.insert(_("BSLASH"), Key_Backslash);
  601. k.insert(_("DEL"), Key_Delete);
  602. k.insert(_("DELETE"), Key_Delete);
  603. k.insert(_("KDEL"), Key_Delete);
  604. k.insert(_("UP"), Key_Up);
  605. k.insert(_("DOWN"), Key_Down);
  606. k.insert(_("LEFT"), Key_Left);
  607. k.insert(_("RIGHT"), Key_Right);
  608. k.insert(_("LT"), Key_Less);
  609. k.insert(_("F1"), Key_F1);
  610. k.insert(_("F2"), Key_F2);
  611. k.insert(_("F3"), Key_F3);
  612. k.insert(_("F4"), Key_F4);
  613. k.insert(_("F5"), Key_F5);
  614. k.insert(_("F6"), Key_F6);
  615. k.insert(_("F7"), Key_F7);
  616. k.insert(_("F8"), Key_F8);
  617. k.insert(_("F9"), Key_F9);
  618. k.insert(_("F10"), Key_F10);
  619. k.insert(_("F11"), Key_F11);
  620. k.insert(_("F12"), Key_F12);
  621. k.insert(_("F13"), Key_F13);
  622. k.insert(_("F14"), Key_F14);
  623. k.insert(_("F15"), Key_F15);
  624. k.insert(_("F16"), Key_F16);
  625. k.insert(_("F17"), Key_F17);
  626. k.insert(_("F18"), Key_F18);
  627. k.insert(_("F19"), Key_F19);
  628. k.insert(_("F20"), Key_F20);
  629. k.insert(_("F21"), Key_F21);
  630. k.insert(_("F22"), Key_F22);
  631. k.insert(_("F23"), Key_F23);
  632. k.insert(_("F24"), Key_F24);
  633. k.insert(_("F25"), Key_F25);
  634. k.insert(_("F26"), Key_F26);
  635. k.insert(_("F27"), Key_F27);
  636. k.insert(_("F28"), Key_F28);
  637. k.insert(_("F29"), Key_F29);
  638. k.insert(_("F30"), Key_F30);
  639. k.insert(_("F31"), Key_F31);
  640. k.insert(_("F32"), Key_F32);
  641. k.insert(_("F33"), Key_F33);
  642. k.insert(_("F34"), Key_F34);
  643. k.insert(_("F35"), Key_F35);
  644. k.insert(_("INSERT"), Key_Insert);
  645. k.insert(_("INS"), Key_Insert);
  646. k.insert(_("KINSERT"), Key_Insert);
  647. k.insert(_("HOME"), Key_Home);
  648. k.insert(_("END"), Key_End);
  649. k.insert(_("PAGEUP"), Key_PageUp);
  650. k.insert(_("PAGEDOWN"), Key_PageDown);
  651. k.insert(_("KPLUS"), Key_Plus);
  652. k.insert(_("KMINUS"), Key_Minus);
  653. k.insert(_("KDIVIDE"), Key_Slash);
  654. k.insert(_("KMULTIPLY"), Key_Asterisk);
  655. k.insert(_("KENTER"), Key_Enter);
  656. k.insert(_("KPOINT"), Key_Period);
  657. return k;
  658. }
  659. Range::Range()
  660. : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
  661. {}
  662. Range::Range(int b, int e, RangeMode m)
  663. : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
  664. {}
  665. QString Range::toString() const
  666. {
  667. return QString::fromLatin1("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
  668. .arg(rangemode);
  669. }
  670. QDebug operator<<(QDebug ts, const Range &range)
  671. {
  672. return ts << '[' << range.beginPos << ',' << range.endPos << ']';
  673. }
  674. ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
  675. : cmd(c), hasBang(false), args(a), range(r), count(1)
  676. {}
  677. bool ExCommand::matches(const QString &min, const QString &full) const
  678. {
  679. return cmd.startsWith(min) && full.startsWith(cmd);
  680. }
  681. QDebug operator<<(QDebug ts, const ExCommand &cmd)
  682. {
  683. return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
  684. }
  685. QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
  686. {
  687. foreach (const QTextEdit::ExtraSelection &sel, sels)
  688. ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
  689. return ts;
  690. }
  691. QString quoteUnprintable(const QString &ba)
  692. {
  693. QString res;
  694. for (int i = 0, n = ba.size(); i != n; ++i) {
  695. const QChar c = ba.at(i);
  696. const int cc = c.unicode();
  697. if (c.isPrint())
  698. res += c;
  699. else if (cc == QLatin1Char('\n'))
  700. res += _("<CR>");
  701. else
  702. res += QString::fromLatin1("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
  703. }
  704. return res;
  705. }
  706. static bool startsWithWhitespace(const QString &str, int col)
  707. {
  708. QTC_ASSERT(str.size() >= col, return false);
  709. for (int i = 0; i < col; ++i) {
  710. uint u = str.at(i).unicode();
  711. if (u != QLatin1Char(' ') && u != QLatin1Char('\t'))
  712. return false;
  713. }
  714. return true;
  715. }
  716. inline QString msgMarkNotSet(const QString &text)
  717. {
  718. return FakeVimHandler::tr("Mark '%1' not set").arg(text);
  719. }
  720. class Input
  721. {
  722. public:
  723. // Remove some extra "information" on Mac.
  724. static int cleanModifier(int m) { return m & ~Qt::KeypadModifier; }
  725. Input()
  726. : m_key(0), m_xkey(0), m_modifiers(0) {}
  727. explicit Input(QChar x)
  728. : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x)
  729. {
  730. if (x.isUpper())
  731. m_modifiers = Qt::ShiftModifier;
  732. else if (x.isLower())
  733. m_key = x.toUpper().unicode();
  734. }
  735. Input(int k, int m, const QString &t = QString())
  736. : m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
  737. {
  738. // On Mac, QKeyEvent::text() returns non-empty strings for
  739. // cursor keys. This breaks some of the logic later on
  740. // relying on text() being empty for "special" keys.
  741. // FIXME: Check the real conditions.
  742. if (m_text.size() == 1 && m_text.at(0).unicode() < ' ')
  743. m_text.clear();
  744. // Set text only if input is ascii key without control modifier.
  745. if (m_text.isEmpty() && k <= 0x7f && (m & (HostOsInfo::controlModifier())) == 0) {
  746. QChar c = QChar::fromAscii(k);
  747. m_text = QString((m & ShiftModifier) != 0 ? c.toUpper() : c.toLower());
  748. }
  749. // m_xkey is only a cache.
  750. m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
  751. }
  752. bool isValid() const
  753. {
  754. return m_key != 0 || !m_text.isNull();
  755. }
  756. bool isDigit() const
  757. {
  758. return m_xkey >= QLatin1Char('0') && m_xkey <= QLatin1Char('9');
  759. }
  760. bool isKey(int c) const
  761. {
  762. return !m_modifiers && m_key == c;
  763. }
  764. bool isBackspace() const
  765. {
  766. return m_key == Key_Backspace || isControl('h');
  767. }
  768. bool isReturn() const
  769. {
  770. return m_key == QLatin1Char('\n') || m_key == Key_Return || m_key == Key_Enter;
  771. }
  772. bool isEscape() const
  773. {
  774. return isKey(Key_Escape) || isKey(27) || isControl('c')
  775. || isControl(Key_BracketLeft);
  776. }
  777. bool is(int c) const
  778. {
  779. return m_xkey == c && m_modifiers != int(HostOsInfo::controlModifier());
  780. }
  781. bool isControl(int c) const
  782. {
  783. return m_modifiers == int(HostOsInfo::controlModifier())
  784. && (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
  785. }
  786. bool isShift(int c) const
  787. {
  788. return m_modifiers == Qt::ShiftModifier && m_xkey == c;
  789. }
  790. bool operator<(const Input &a) const
  791. {
  792. if (m_key != a.m_key)
  793. return m_key < a.m_key;
  794. // Text for some mapped key cannot be determined (e.g. <C-J>) so if text is not set for
  795. // one of compared keys ignore it.
  796. if (!m_text.isEmpty() && !a.m_text.isEmpty())
  797. return m_text < a.m_text;
  798. return m_modifiers < a.m_modifiers;
  799. }
  800. bool operator==(const Input &a) const
  801. {
  802. return !(*this < a || a < *this);
  803. }
  804. bool operator!=(const Input &a) const { return !operator==(a); }
  805. QString text() const { return m_text; }
  806. QChar asChar() const
  807. {
  808. return (m_text.size() == 1 ? m_text.at(0) : QChar());
  809. }
  810. int key() const { return m_key; }
  811. QChar raw() const
  812. {
  813. if (m_key == Key_Tab)
  814. return QLatin1Char('\t');
  815. if (m_key == Key_Return)
  816. return QLatin1Char('\n');
  817. return m_key;
  818. }
  819. QString toString() const
  820. {
  821. bool hasCtrl = m_modifiers & int(HostOsInfo::controlModifier());
  822. QString key = vimKeyNames().key(m_key);
  823. if (key.isEmpty())
  824. key = QChar(m_xkey);
  825. else
  826. key = QLatin1Char('<') + key + QLatin1Char('>');
  827. return (hasCtrl ? QString::fromLatin1("^") : QString()) + key;
  828. }
  829. QDebug dump(QDebug ts) const
  830. {
  831. return ts << m_key << '-' << m_modifiers << '-'
  832. << quoteUnprintable(m_text);
  833. }
  834. private:
  835. int m_key;
  836. int m_xkey;
  837. int m_modifiers;
  838. QString m_text;
  839. };
  840. // mapping to <Nop> (do nothing)
  841. static const Input Nop(-1, -1, QString());
  842. QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }
  843. class Inputs : public QVector<Input>
  844. {
  845. public:
  846. Inputs() : m_noremap(true), m_silent(false) {}
  847. explicit Inputs(const QString &str, bool noremap = true, bool silent = false)
  848. : m_noremap(noremap), m_silent(silent)
  849. {
  850. parseFrom(str);
  851. }
  852. bool noremap() const { return m_noremap; }
  853. bool silent() const { return m_silent; }
  854. private:
  855. void parseFrom(const QString &str);
  856. bool m_noremap;
  857. bool m_silent;
  858. };
  859. static Input parseVimKeyName(const QString &keyName)
  860. {
  861. if (keyName.length() == 1)
  862. return Input(keyName.at(0));
  863. const QStringList keys = keyName.split(QLatin1Char('-'));
  864. const int len = keys.length();
  865. if (len == 1 && keys.at(0) == _("nop"))
  866. return Nop;
  867. int mods = NoModifier;
  868. for (int i = 0; i < len - 1; ++i) {
  869. const QString &key = keys[i].toUpper();
  870. if (key == _("S"))
  871. mods |= Qt::ShiftModifier;
  872. else if (key == _("C"))
  873. mods |= HostOsInfo::controlModifier();
  874. else
  875. return Input();
  876. }
  877. if (!keys.isEmpty()) {
  878. const QString key = keys.last();
  879. if (key.length() == 1) {
  880. // simple character
  881. QChar c = key.at(0).toUpper();
  882. return Input(c.unicode(), mods);
  883. }
  884. // find key name
  885. QMap<QString, int>::ConstIterator it = vimKeyNames().constFind(key.toUpper());
  886. if (it != vimKeyNames().end())
  887. return Input(*it, mods);
  888. }
  889. return Input();
  890. }
  891. void Inputs::parseFrom(const QString &str)
  892. {
  893. const int n = str.size();
  894. for (int i = 0; i < n; ++i) {
  895. uint c = str.at(i).unicode();
  896. if (c == QLatin1Char('<')) {
  897. int j = str.indexOf(QLatin1Char('>'), i);
  898. Input input;
  899. if (j != -1) {
  900. const QString key = str.mid(i+1, j - i - 1);
  901. if (!key.contains(QLatin1Char('<')))
  902. input = parseVimKeyName(key);
  903. }
  904. if (input.isValid()) {
  905. append(input);
  906. i = j;
  907. } else {
  908. append(Input(QLatin1Char(c)));
  909. }
  910. } else {
  911. append(Input(QLatin1Char(c)));
  912. }
  913. }
  914. }
  915. class History
  916. {
  917. public:
  918. History() : m_items(QString()), m_index(0) {}
  919. void append(const QString &item);
  920. const QString &move(const QStringRef &prefix, int skip);
  921. const QString &current() const { return m_items[m_index]; }
  922. const QStringList &items() const { return m_items; }
  923. void restart() { m_index = m_items.size() - 1; }
  924. private:
  925. // Last item is always empty or current search prefix.
  926. QStringList m_items;
  927. int m_index;
  928. };
  929. void History::append(const QString &item)
  930. {
  931. if (item.isEmpty())
  932. return;
  933. m_items.pop_back();
  934. m_items.removeAll(item);
  935. m_items << item << QString();
  936. restart();
  937. }
  938. const QString &History::move(const QStringRef &prefix, int skip)
  939. {
  940. if (!current().startsWith(prefix))
  941. restart();
  942. if (m_items.last() != prefix)
  943. m_items[m_items.size() - 1] = prefix.toString();
  944. int i = m_index + skip;
  945. if (!prefix.isEmpty())
  946. for (; i >= 0 && i < m_items.size() && !m_items[i].startsWith(prefix); i += skip);
  947. if (i >= 0 && i < m_items.size())
  948. m_index = i;
  949. return current();
  950. }
  951. // Command line buffer with prompt (i.e. :, / or ? characters), text contents and cursor position.
  952. class CommandBuffer
  953. {
  954. public:
  955. CommandBuffer() : m_pos(0), m_anchor(0), m_userPos(0), m_historyAutoSave(true) {}
  956. void setPrompt(const QChar &prompt) { m_prompt = prompt; }
  957. void setContents(const QString &s) { m_buffer = s; m_anchor = m_pos = s.size(); }
  958. void setContents(const QString &s, int pos, int anchor = -1)
  959. {
  960. m_buffer = s; m_pos = m_userPos = pos; m_anchor = anchor >= 0 ? anchor : pos;
  961. }
  962. QStringRef userContents() const { return m_buffer.leftRef(m_userPos); }
  963. const QChar &prompt() const { return m_prompt; }
  964. const QString &contents() const { return m_buffer; }
  965. bool isEmpty() const { return m_buffer.isEmpty(); }
  966. int cursorPos() const { return m_pos; }
  967. int anchorPos() const { return m_anchor; }
  968. bool hasSelection() const { return m_pos != m_anchor; }
  969. void insertChar(QChar c) { m_buffer.insert(m_pos++, c); m_anchor = m_userPos = m_pos; }
  970. void insertText(const QString &s)
  971. {
  972. m_buffer.insert(m_pos, s); m_anchor = m_userPos = m_pos = m_pos + s.size();
  973. }
  974. void deleteChar() { if (m_pos) m_buffer.remove(--m_pos, 1); m_anchor = m_userPos = m_pos; }
  975. void moveLeft() { if (m_pos) m_userPos = --m_pos; }
  976. void moveRight() { if (m_pos < m_buffer.size()) m_userPos = ++m_pos; }
  977. void moveStart() { m_userPos = m_pos = 0; }
  978. void moveEnd() { m_userPos = m_pos = m_buffer.size(); }
  979. void setHistoryAutoSave(bool autoSave) { m_historyAutoSave = autoSave; }
  980. void historyDown() { setContents(m_history.move(userContents(), 1)); }
  981. void historyUp() { setContents(m_history.move(userContents(), -1)); }
  982. const QStringList &historyItems() const { return m_history.items(); }
  983. void historyPush(const QString &item = QString())
  984. {
  985. m_history.append(item.isNull() ? contents() : item);
  986. }
  987. void clear()
  988. {
  989. if (m_historyAutoSave)
  990. historyPush();
  991. m_buffer.clear();
  992. m_anchor = m_userPos = m_pos = 0;
  993. }
  994. QString display() const
  995. {
  996. QString msg(m_prompt);
  997. for (int i = 0; i != m_buffer.size(); ++i) {
  998. const QChar c = m_buffer.at(i);
  999. if (c.unicode() < 32) {
  1000. msg += QLatin1Char('^');
  1001. msg += QLatin1Char(c.unicode() + 64);
  1002. } else {
  1003. msg += c;
  1004. }
  1005. }
  1006. return msg;
  1007. }
  1008. void deleteSelected()
  1009. {
  1010. if (m_pos < m_anchor) {
  1011. m_buffer.remove(m_pos, m_anchor - m_pos);
  1012. m_anchor = m_pos;
  1013. } else {
  1014. m_buffer.remove(m_anchor, m_pos - m_anchor);
  1015. m_pos = m_anchor;
  1016. }
  1017. }
  1018. bool handleInput(const Input &input)
  1019. {
  1020. if (input.isShift(Key_Left)) {
  1021. moveLeft();
  1022. } else if (input.isShift(Key_Right)) {
  1023. moveRight();
  1024. } else if (input.isShift(Key_Home)) {
  1025. moveStart();
  1026. } else if (input.isShift(Key_End)) {
  1027. moveEnd();
  1028. } else if (input.isKey(Key_Left)) {
  1029. moveLeft();
  1030. m_anchor = m_pos;
  1031. } else if (input.isKey(Key_Right)) {
  1032. moveRight();
  1033. m_anchor = m_pos;
  1034. } else if (input.isKey(Key_Home)) {
  1035. moveStart();
  1036. m_anchor = m_pos;
  1037. } else if (input.isKey(Key_End)) {
  1038. moveEnd();
  1039. m_anchor = m_pos;
  1040. } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
  1041. historyUp();
  1042. } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
  1043. historyDown();
  1044. } else if (input.isKey(Key_Delete)) {
  1045. if (hasSelection()) {
  1046. deleteSelected();
  1047. } else {
  1048. if (m_pos < m_buffer.size())
  1049. m_buffer.remove(m_pos, 1);
  1050. else
  1051. deleteChar();
  1052. }
  1053. } else if (!input.text().isEmpty()) {
  1054. if (hasSelection())
  1055. deleteSelected();
  1056. insertText(input.text());
  1057. } else {
  1058. return false;
  1059. }
  1060. return true;
  1061. }
  1062. private:
  1063. QString m_buffer;
  1064. QChar m_prompt;
  1065. History m_history;
  1066. int m_pos;
  1067. int m_anchor;
  1068. int m_userPos; // last position of inserted text (for retrieving history items)
  1069. bool m_historyAutoSave; // store items to history on clear()?
  1070. };
  1071. // Mappings for a specific mode (trie structure)
  1072. class ModeMapping : public QMap<Input, ModeMapping>
  1073. {
  1074. public:
  1075. const Inputs &value() const { return m_value; }
  1076. void setValue(const Inputs &value) { m_value = value; }
  1077. private:
  1078. Inputs m_value;
  1079. };
  1080. // Mappings for all modes
  1081. typedef QHash<char, ModeMapping> Mappings;
  1082. // Iterator for mappings
  1083. class MappingsIterator : public QVector<ModeMapping::Iterator>
  1084. {
  1085. public:
  1086. MappingsIterator(Mappings *mappings, char mode = -1, const Inputs &inputs = Inputs())
  1087. : m_parent(mappings)
  1088. {
  1089. reset(mode);
  1090. walk(inputs);
  1091. }
  1092. // Reset iterator state. Keep previous mode if 0.
  1093. void reset(char mode = 0)
  1094. {
  1095. clear();
  1096. m_lastValid = -1;
  1097. m_invalidInputCount = 0;
  1098. if (mode != 0) {
  1099. m_mode = mode;
  1100. if (mode != -1)
  1101. m_modeMapping = m_parent->find(mode);
  1102. }
  1103. }
  1104. bool isValid() const { return !empty(); }
  1105. // Return true if mapping can be extended.
  1106. bool canExtend() const { return isValid() && !last()->empty(); }
  1107. // Return true if this mapping can be used.
  1108. bool isComplete() const { return m_lastValid != -1; }
  1109. // Return size of current map.
  1110. int mapLength() const { return m_lastValid + 1; }
  1111. int invalidInputCount() const { return m_invalidInputCount; }
  1112. bool walk(const Input &input)
  1113. {
  1114. if (m_modeMapping == m_parent->end())
  1115. return false;
  1116. if (!input.isValid()) {
  1117. m_invalidInputCount += 1;
  1118. return true;
  1119. }
  1120. ModeMapping::Iterator it;
  1121. if (isValid()) {
  1122. it = last()->find(input);
  1123. if (it == last()->end())
  1124. return false;
  1125. } else {
  1126. it = m_modeMapping->find(input);
  1127. if (it == m_modeMapping->end())
  1128. return false;
  1129. }
  1130. if (!it->value().isEmpty())
  1131. m_lastValid = size();
  1132. append(it);
  1133. return true;
  1134. }
  1135. bool walk(const Inputs &inputs)
  1136. {
  1137. foreach (const Input &input, inputs) {
  1138. if (!walk(input))
  1139. return false;
  1140. }
  1141. return true;
  1142. }
  1143. // Return current mapped value. Iterator must be valid.
  1144. const Inputs &inputs() const
  1145. {
  1146. return at(m_lastValid)->value();
  1147. }
  1148. void remove()
  1149. {
  1150. if (isValid()) {
  1151. if (canExtend()) {
  1152. last()->setValue(Inputs());
  1153. } else {
  1154. if (size() > 1) {
  1155. while (last()->empty()) {
  1156. at(size() - 2)->erase(last());
  1157. pop_back();
  1158. if (size() == 1 || !last()->value().isEmpty())
  1159. break;
  1160. }
  1161. if (last()->empty() && last()->value().isEmpty())
  1162. m_modeMapping->erase(last());
  1163. } else if (last()->empty() && !last()->value().isEmpty()) {
  1164. m_modeMapping->erase(last());
  1165. }
  1166. }
  1167. }
  1168. }
  1169. void setInputs(const Inputs &key, const Inputs &inputs, bool unique = false)
  1170. {
  1171. ModeMapping *current = &(*m_parent)[m_mode];
  1172. foreach (const Input &input, key)
  1173. current = &(*current)[input];
  1174. if (!unique || current->value().isEmpty())
  1175. current->setValue(inputs);
  1176. }
  1177. private:
  1178. Mappings *m_parent;
  1179. Mappings::Iterator m_modeMapping;
  1180. int m_lastValid;
  1181. int m_invalidInputCount;
  1182. char m_mode;
  1183. };
  1184. // state of current mapping
  1185. struct MappingState {
  1186. MappingState()
  1187. : maxMapDepth(1000), noremap(false), silent(false) {}
  1188. MappingState(int depth, bool noremap, bool silent)
  1189. : maxMapDepth(depth), noremap(noremap), silent(silent) {}
  1190. int maxMapDepth;
  1191. bool noremap;
  1192. bool silent;
  1193. };
  1194. class FakeVimHandler::Private : public QObject
  1195. {
  1196. Q_OBJECT
  1197. public:
  1198. Private(FakeVimHandler *parent, QWidget *widget);
  1199. EventResult handleEvent(QKeyEvent *ev);
  1200. bool wantsOverride(QKeyEvent *ev);
  1201. bool parseExCommmand(QString *line, ExCommand *cmd);
  1202. bool parseLineRange(QString *line, ExCommand *cmd);
  1203. int parseLineAddress(QString *cmd);
  1204. void parseRangeCount(const QString &line, Range *range) const;
  1205. void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
  1206. void handleExCommand(const QString &cmd);
  1207. void installEventFilter();
  1208. void passShortcuts(bool enable);
  1209. void setupWidget();
  1210. void restoreWidget(int tabSize);
  1211. friend class FakeVimHandler;
  1212. void init();
  1213. void focus();
  1214. void enterFakeVim(); // Call before any FakeVim processing (import cursor position from editor)
  1215. void leaveFakeVim(); // Call after any FakeVim processing (export cursor position to editor)
  1216. EventResult handleKey(const Input &input);
  1217. EventResult handleDefaultKey(const Input &input);
  1218. void handleMappedKeys();
  1219. void unhandleMappedKeys();
  1220. EventResult handleInsertMode(const Input &);
  1221. EventResult handleReplaceMode(const Input &);
  1222. EventResult handleCommandMode(const Input &);
  1223. // return true only if input in current mode and sub-mode was correctly handled
  1224. bool handleEscape();
  1225. bool handleNoSubMode(const Input &);
  1226. bool handleChangeDeleteSubModes(const Input &);
  1227. bool handleReplaceSubMode(const Input &);
  1228. bool handleFilterSubMode(const Input &);
  1229. bool handleRegisterSubMode(const Input &);
  1230. bool handleShiftSubMode(const Input &);
  1231. bool handleChangeCaseSubMode(const Input &);
  1232. bool handleWindowSubMode(const Input &);
  1233. bool handleYankSubMode(const Input &);
  1234. bool handleZSubMode(const Input &);
  1235. bool handleCapitalZSubMode(const Input &);
  1236. bool handleMovement(const Input &);
  1237. EventResult handleExMode(const Input &);
  1238. EventResult handleSearchSubSubMode(const Input &);
  1239. bool handleCommandSubSubMode(const Input &);
  1240. void fixSelection(); // Fix selection according to current range, move and command modes.
  1241. void finishMovement(const QString &dotCommandMovement = QString());
  1242. void finishMovement(const QString &dotCommandMovement, int count);
  1243. void resetCommandMode();
  1244. QTextCursor search(const SearchData &sd, int startPos, int count, bool showMessages);
  1245. void search(const SearchData &sd, bool showMessages = true);
  1246. void searchNext(bool forward = true);
  1247. void searchBalanced(bool forward, QChar needle, QChar other);
  1248. void highlightMatches(const QString &needle);
  1249. void stopIncrementalFind();
  1250. void updateFind(bool isComplete);
  1251. int mvCount() const { return m_mvcount.isEmpty() ? 1 : m_mvcount.toInt(); }
  1252. int opCount() const { return m_opcount.isEmpty() ? 1 : m_opcount.toInt(); }
  1253. int count() const { return mvCount() * opCount(); }
  1254. QTextBlock block() const { return cursor().block(); }
  1255. int leftDist() const { return position() - block().position(); }
  1256. int rightDist() const { return block().length() - leftDist() - 1; }
  1257. bool atBlockStart() const { return cursor().atBlockStart(); }
  1258. bool atBlockEnd() const { return cursor().atBlockEnd(); }
  1259. bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
  1260. bool atDocumentEnd() const { return position() >= lastPositionInDocument(); }
  1261. bool atDocumentStart() const { return cursor().atStart(); }
  1262. bool atEmptyLine(const QTextCursor &tc = QTextCursor()) const;
  1263. bool atBoundary(bool end, bool simple, bool onlyWords = false,
  1264. const QTextCursor &tc = QTextCursor()) const;
  1265. bool atWordBoundary(bool end, bool simple, const QTextCursor &tc = QTextCursor()) const;
  1266. bool atWordStart(bool simple, const QTextCursor &tc = QTextCursor()) const;
  1267. bool atWordEnd(bool simple, const QTextCursor &tc = QTextCursor()) const;
  1268. bool isFirstNonBlankOnLine(int pos);
  1269. int lastPositionInDocument(bool ignoreMode = false) const; // Returns last valid position in doc.
  1270. int firstPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos
  1271. int lastPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos
  1272. int lineForPosition(int pos) const; // 1 based line, 0 based pos
  1273. QString lineContents(int line) const; // 1 based line
  1274. void setLineContents(int line, const QString &contents); // 1 based line
  1275. int blockBoundary(const QString &left, const QString &right,
  1276. bool end, int count) const; // end or start position of current code block
  1277. int lineNumber(const QTextBlock &block) const;
  1278. int linesOnScreen() const;
  1279. int columnsOnScreen() const;
  1280. int linesInDocument() const;
  1281. // The following use all zero-based counting.
  1282. int cursorLineOnScreen() const;
  1283. int cursorLine() const;
  1284. int cursorBlockNumber() const; // "." address
  1285. int physicalCursorColumn() const; // as stored in the data
  1286. int logicalCursorColumn() const; // as visible on screen
  1287. int physicalToLogicalColumn(int physical, const QString &text) const;
  1288. int logicalToPhysicalColumn(int logical, const QString &text) const;
  1289. Column cursorColumn() const; // as visible on screen
  1290. int firstVisibleLine() const;
  1291. void scrollToLine(int line);
  1292. void scrollUp(int count);
  1293. void scrollDown(int count) { scrollUp(-count); }
  1294. void alignViewportToCursor(Qt::AlignmentFlag align, int line = -1,
  1295. bool moveToNonBlank = false);
  1296. void setCursorPosition(const CursorPosition &p);
  1297. void setCursorPosition(QTextCursor *tc, const CursorPosition &p);
  1298. // Helper functions for indenting/
  1299. bool isElectricCharacter(QChar c) const;
  1300. void indentSelectedText(QChar lastTyped = QChar());
  1301. void indentText(const Range &range, QChar lastTyped = QChar());
  1302. void shiftRegionLeft(int repeat = 1);
  1303. void shiftRegionRight(int repeat = 1);
  1304. void moveToFirstNonBlankOnLine();
  1305. void moveToFirstNonBlankOnLine(QTextCursor *tc);
  1306. void moveToTargetColumn();
  1307. void setTargetColumn() {
  1308. m_targetColumn = logicalCursorColumn();
  1309. m_visualTargetColumn = m_targetColumn;
  1310. //qDebug() << "TARGET: " << m_targetColumn;
  1311. }
  1312. void moveToMatchingParanthesis();
  1313. void moveToBoundary(bool simple, bool forward = true);
  1314. void moveToNextBoundary(bool end, int count, bool simple, bool forward);
  1315. void moveToNextBoundaryStart(int count, bool simple, bool forward = true);
  1316. void moveToNextBoundaryEnd(int count, bool simple, bool forward = true);
  1317. void moveToBoundaryStart(int count, bool simple, bool forward = true);
  1318. void moveToBoundaryEnd(int count, bool simple, bool forward = true);
  1319. void moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines);
  1320. void moveToNextWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
  1321. void moveToNextWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
  1322. void moveToWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
  1323. void moveToWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
  1324. // Convenience wrappers to reduce line noise.
  1325. void moveToStartOfLine();
  1326. void moveToEndOfLine();
  1327. void moveBehindEndOfLine();
  1328. void moveUp(int n = 1) { moveDown(-n); }
  1329. void moveDown(int n = 1);
  1330. void dump(const char *msg) const {
  1331. qDebug() << msg << "POS: " << anchor() << position()
  1332. << "EXT: " << m_oldExternalAnchor << m_oldExternalPosition
  1333. << "INT: " << m_oldInternalAnchor << m_oldInternalPosition
  1334. << "VISUAL: " << m_visualMode;
  1335. }
  1336. void moveRight(int n = 1) {
  1337. //dump("RIGHT 1");
  1338. QTextCursor tc = cursor();
  1339. tc.movePosition(Right, KeepAnchor, n);
  1340. setCursor(tc);
  1341. if (atEndOfLine())
  1342. emit q->fold(1, false);
  1343. //dump("RIGHT 2");
  1344. }
  1345. void moveLeft(int n = 1) {
  1346. QTextCursor tc = cursor();
  1347. tc.movePosition(Left, KeepAnchor, n);
  1348. setCursor(tc);
  1349. }
  1350. void setAnchor() {
  1351. QTextCursor tc = cursor();
  1352. tc.setPosition(tc.position(), MoveAnchor);
  1353. setCursor(tc);
  1354. }
  1355. void setAnchor(int position) {
  1356. QTextCursor tc = cursor();
  1357. tc.setPosition(tc.anchor(), MoveAnchor);
  1358. tc.setPosition(position, KeepAnchor);
  1359. setCursor(tc);
  1360. }
  1361. void setPosition(int position) {
  1362. QTextCursor tc = cursor();
  1363. tc.setPosition(position, KeepAnchor);
  1364. setCursor(tc);
  1365. }
  1366. void setAnchorAndPosition(int anchor, int position) {
  1367. QTextCursor tc = cursor();
  1368. tc.setPosition(anchor, MoveAnchor);
  1369. tc.setPosition(position, KeepAnchor);
  1370. setCursor(tc);
  1371. }
  1372. // Workaround for occational crash when setting text cursor while in edit block.
  1373. QTextCursor m_cursor;
  1374. QTextCursor cursor() const {
  1375. if (m_editBlockLevel > 0)
  1376. return m_cursor;
  1377. return EDITOR(textCursor());
  1378. }
  1379. void setCursor(const QTextCursor &tc) {
  1380. m_cursor = tc;
  1381. if (m_editBlockLevel == 0)
  1382. EDITOR(setTextCursor(tc));
  1383. }
  1384. bool moveToPreviousParagraph(int count) { return moveToNextParagraph(-count); }
  1385. bool moveToNextParagraph(int count);
  1386. bool handleFfTt(QString key);
  1387. void enterInsertMode();
  1388. void initVisualBlockInsertMode(QChar command);
  1389. void enterReplaceMode();
  1390. void enterCommandMode(Mode returnToMode = CommandMode);
  1391. void enterExMode(const QString &contents = QString());
  1392. void showMessage(MessageLevel level, const QString &msg);
  1393. void clearMessage() { showMessage(MessageInfo, QString()); }
  1394. void notImplementedYet();
  1395. void updateMiniBuffer();
  1396. void updateSelection();
  1397. void updateHighlights();
  1398. void updateCursorShape();
  1399. QWidget *editor() const;
  1400. QTextDocument *document() const { return EDITOR(document()); }
  1401. QChar characterAtCursor() const
  1402. { return document()->characterAt(position()); }
  1403. int m_editBlockLevel; // current level of edit blocks
  1404. void joinPreviousEditBlock();
  1405. void beginEditBlock(bool rememberPosition = true);
  1406. void beginLargeEditBlock() { beginEditBlock(false); }
  1407. void endEditBlock();
  1408. void breakEditBlock() { m_breakEditBlock = true; }
  1409. bool isVisualMode() const { return m_visualMode != NoVisualMode; }
  1410. bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
  1411. bool isVisualCharMode() const { return m_visualMode == VisualCharMode; }
  1412. bool isVisualLineMode() const { return m_visualMode == VisualLineMode; }
  1413. bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; }
  1414. char currentModeCode() const;
  1415. void updateEditor();
  1416. void selectTextObject(bool simple, bool inner);
  1417. void selectWordTextObject(bool inner);
  1418. void selectWORDTextObject(bool inner);
  1419. void selectSentenceTextObject(bool inner);
  1420. void selectParagraphTextObject(bool inner);
  1421. bool changeNumberTextObject(int count);
  1422. // return true only if cursor is in a block delimited with correct characters
  1423. bool selectBlockTextObject(bool inner, char left, char right);
  1424. bool selectQuotedStringTextObject(bool inner, const QString &quote);
  1425. Q_SLOT void importSelection();
  1426. void exportSelection();
  1427. void recordInsertion(const QString &insert = QString());
  1428. void ensureCursorVisible();
  1429. void insertInInsertMode(const QString &text);
  1430. public:
  1431. QTextEdit *m_textedit;
  1432. QPlainTextEdit *m_plaintextedit;
  1433. bool m_wasReadOnly; // saves read-only state of document
  1434. FakeVimHandler *q;
  1435. Mode m_mode;
  1436. bool m_passing; // let the core see the next event
  1437. SubMode m_submode;
  1438. SubSubMode m_subsubmode;
  1439. Input m_subsubdata;
  1440. int m_oldExternalPosition; // copy from last event to check for external changes
  1441. int m_oldExternalAnchor;
  1442. int m_oldInternalPosition; // copy from last event to check for external changes
  1443. int m_oldInternalAnchor;
  1444. int m_oldPosition; // FIXME: Merge with above.
  1445. int m_register;
  1446. QString m_mvcount;
  1447. QString m_opcount;
  1448. MoveType m_movetype;
  1449. RangeMode m_rangemode;
  1450. bool m_visualBlockInsert;
  1451. bool m_fakeEnd;
  1452. bool m_anchorPastEnd;
  1453. bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
  1454. int m_gflag; // whether current command started with 'g'
  1455. QString m_currentFileName;
  1456. int m_findStartPosition;
  1457. QString m_lastInsertion;
  1458. bool m_breakEditBlock;
  1459. int anchor() const { return cursor().anchor(); }
  1460. int position() const { return cursor().position(); }
  1461. struct TransformationData
  1462. {
  1463. TransformationData(const QString &s, const QVariant &d)
  1464. : from(s), extraData(d) {}
  1465. QString from;
  1466. QString to;
  1467. QVariant extraData;
  1468. };
  1469. typedef void (Private::*Transformation)(TransformationData *td);
  1470. void transformText(const Range &range, Transformation transformation,
  1471. const QVariant &extraData = QVariant());
  1472. void insertText(const Register &reg);
  1473. void removeText(const Range &range);
  1474. void removeTransform(TransformationData *td);
  1475. void invertCase(const Range &range);
  1476. void invertCaseTransform(TransformationData *td);
  1477. void upCase(const Range &range);
  1478. void upCaseTransform(TransformationData *td);
  1479. void downCase(const Range &range);
  1480. void downCaseTransform(TransformationData *td);
  1481. void replaceText(const Range &range, const QString &str);
  1482. void replaceByStringTransform(TransformationData *td);
  1483. void replaceByCharTransform(TransformationData *td);
  1484. QString selectText(const Range &range) const;
  1485. void setCurrentRange(const Range &range);
  1486. Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
  1487. void yankText(const Range &range, int toregister = '"');
  1488. void pasteText(bool afterCursor);
  1489. void joinLines(int count, bool preserveSpace = false);
  1490. // undo handling
  1491. int revision() const { return document()->availableUndoSteps(); }
  1492. void undoRedo(bool undo);
  1493. void undo();
  1494. void redo();
  1495. void setUndoPosition(bool overwrite = true);
  1496. // revision -> state
  1497. QStack<State> m_undo;
  1498. QStack<State> m_redo;
  1499. // extra data for '.'
  1500. void replay(const QString &text);
  1501. void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
  1502. void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
  1503. QString visualDotCommand() const;
  1504. // extra data for ';'
  1505. QString m_semicolonCount;
  1506. Input m_semicolonType; // 'f', 'F', 't', 'T'
  1507. QString m_semicolonKey;
  1508. // visual modes
  1509. void toggleVisualMode(VisualMode visualMode);
  1510. void leaveVisualMode();
  1511. VisualMode m_visualMode;
  1512. VisualMode m_lastVisualMode;
  1513. bool m_lastVisualModeInverted;
  1514. // marks
  1515. Mark mark(QChar code) const;
  1516. void setMark(QChar code, CursorPosition position);
  1517. // jump to valid mark return true if mark is valid and local
  1518. bool jumpToMark(QChar mark, bool backTickMode);
  1519. // update marks on undo/redo
  1520. void updateMarks(const Marks &newMarks);
  1521. Marks m_marks; // local marks
  1522. // vi style configuration
  1523. QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
  1524. bool hasConfig(int code) const { return config(code).toBool(); }
  1525. bool hasConfig(int code, const char *value) const // FIXME
  1526. { return config(code).toString().contains(_(value)); }
  1527. int m_targetColumn; // -1 if past end of line
  1528. int m_visualTargetColumn; // 'l' can move past eol in visual mode only
  1529. // auto-indent
  1530. QString tabExpand(int len) const;
  1531. Column indentation(const QString &line) const;
  1532. void insertAutomaticIndentation(bool goingDown);
  1533. bool removeAutomaticIndentation(); // true if something removed
  1534. // number of autoindented characters
  1535. int m_justAutoIndented;
  1536. void handleStartOfLine();
  1537. // register handling
  1538. QString registerContents(int reg) const;
  1539. void setRegister(int reg, const QString &contents, RangeMode mode);
  1540. RangeMode registerRangeMode(int reg) const;
  1541. void getRegisterType(int reg, bool *isClipboard, bool *isSelection) const;
  1542. void recordJump(int position = -1);
  1543. void jump(int distance);
  1544. QStack<CursorPosition> m_jumpListUndo;
  1545. QStack<CursorPosition> m_jumpListRedo;
  1546. CursorPosition m_lastChangePosition;
  1547. QList<QTextEdit::ExtraSelection> m_extraSelections;
  1548. QTextCursor m_searchCursor;
  1549. int m_searchStartPosition;
  1550. int m_searchFromScreenLine;
  1551. QString m_oldNeedle;
  1552. bool handleExCommandHelper(ExCommand &cmd); // Returns success.
  1553. bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
  1554. bool handleExBangCommand(const ExCommand &cmd);
  1555. bool handleExYankDeleteCommand(const ExCommand &cmd);
  1556. bool handleExChangeCommand(const ExCommand &cmd);
  1557. bool handleExMoveCommand(const ExCommand &cmd);
  1558. bool handleExJoinCommand(const ExCommand &cmd);
  1559. bool handleExGotoCommand(const ExCommand &cmd);
  1560. bool handleExHistoryCommand(const ExCommand &cmd);
  1561. bool handleExRegisterCommand(const ExCommand &cmd);
  1562. bool handleExMapCommand(const ExCommand &cmd);
  1563. bool handleExNohlsearchCommand(const ExCommand &cmd);
  1564. bool handleExNormalCommand(const ExCommand &cmd);
  1565. bool handleExReadCommand(const ExCommand &cmd);
  1566. bool handleExUndoRedoCommand(const ExCommand &cmd);
  1567. bool handleExSetCommand(const ExCommand &cmd);
  1568. bool handleExShiftCommand(const ExCommand &cmd);
  1569. bool handleExSourceCommand(const ExCommand &cmd);
  1570. bool handleExSubstituteCommand(const ExCommand &cmd);
  1571. bool handleExWriteCommand(const ExCommand &cmd);
  1572. bool handleExEchoCommand(const ExCommand &cmd);
  1573. void timerEvent(QTimerEvent *ev);
  1574. void setupCharClass();
  1575. int charClass(QChar c, bool simple) const;
  1576. signed char m_charClass[256];
  1577. bool m_ctrlVActive;
  1578. void miniBufferTextEdited(const QString &text, int cursorPos, int anchorPos);
  1579. static struct GlobalData
  1580. {
  1581. GlobalData()
  1582. : mappings(), currentMap(&mappings), inputTimer(-1), currentMessageLevel(MessageInfo),
  1583. lastSearchForward(false), findPending(false), returnToMode(CommandMode)
  1584. {
  1585. // default mapping state - shouldn't be removed
  1586. mapStates << MappingState();
  1587. commandBuffer.setPrompt(QLatin1Char(':'));
  1588. }
  1589. // Repetition.
  1590. QString dotCommand;
  1591. QHash<int, Register> registers;
  1592. // All mappings.
  1593. Mappings mappings;
  1594. // Input.
  1595. Inputs pendingInput;
  1596. MappingsIterator currentMap;
  1597. int inputTimer;
  1598. QStack<MappingState> mapStates;
  1599. // Command line buffers.
  1600. CommandBuffer commandBuffer;
  1601. CommandBuffer searchBuffer;
  1602. // Current mini buffer message.
  1603. QString currentMessage;
  1604. MessageLevel currentMessageLevel;
  1605. QString currentCommand;
  1606. // Search state.
  1607. QString lastSearch;
  1608. bool lastSearchForward;
  1609. bool findPending;
  1610. // Last substitution command.
  1611. QString lastSubstituteFlags;
  1612. QString lastSubstitutePattern;
  1613. QString lastSubstituteReplacement;
  1614. // Global marks.
  1615. Marks marks;
  1616. // Return to insert/replace mode after single command (<C-O>).
  1617. Mode returnToMode;
  1618. } g;
  1619. };
  1620. FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
  1621. FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
  1622. {
  1623. //static PythonHighlighterRules pythonRules;
  1624. q = parent;
  1625. m_textedit = qobject_cast<QTextEdit *>(widget);
  1626. m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
  1627. //new Highlighter(document(), &pythonRules);
  1628. init();
  1629. }
  1630. void FakeVimHandler::Private::init()
  1631. {
  1632. m_mode = CommandMode;
  1633. m_submode = NoSubMode;
  1634. m_subsubmode = NoSubSubMode;
  1635. m_passing = false;
  1636. g.findPending = false;
  1637. m_findStartPosition = -1;
  1638. m_visualBlockInsert = false;
  1639. m_fakeEnd = false;
  1640. m_positionPastEnd = false;
  1641. m_anchorPastEnd = false;
  1642. g.lastSearchForward = true;
  1643. m_register = '"';
  1644. m_gflag = false;
  1645. m_visualMode = NoVisualMode;
  1646. m_lastVisualMode = NoVisualMode;
  1647. m_lastVisualModeInverted = false;
  1648. m_targetColumn = 0;
  1649. m_visualTargetColumn = 0;
  1650. m_movetype = MoveInclusive;
  1651. m_justAutoIndented = 0;
  1652. m_rangemode = RangeCharMode;
  1653. m_ctrlVActive = false;
  1654. m_oldInternalAnchor = -1;
  1655. m_oldInternalPosition = -1;
  1656. m_oldExternalAnchor = -1;
  1657. m_oldExternalPosition = -1;
  1658. m_oldPosition = -1;
  1659. m_breakEditBlock = false;
  1660. m_searchStartPosition = 0;
  1661. m_searchFromScreenLine = 0;
  1662. m_editBlockLevel = 0;
  1663. setupCharClass();
  1664. }
  1665. void FakeVimHandler::Private::focus()
  1666. {
  1667. stopIncrementalFind();
  1668. if (m_mode == CommandMode && g.returnToMode != CommandMode && g.currentCommand.isEmpty()) {
  1669. // Return to insert mode.
  1670. resetCommandMode();
  1671. updateMiniBuffer();
  1672. updateCursorShape();
  1673. }
  1674. }
  1675. void FakeVimHandler::Private::enterFakeVim()
  1676. {
  1677. importSelection();
  1678. QTextCursor tc = cursor();
  1679. // Position changed externally, e.g. by code completion.
  1680. if (tc.position() != m_oldPosition) {
  1681. // record external jump to different line
  1682. if (m_oldPosition != -1 && lineForPosition(m_oldPosition) != lineForPosition(tc.position()))
  1683. recordJump(m_oldPosition);
  1684. setTargetColumn();
  1685. if (atEndOfLine() && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode)
  1686. moveLeft();
  1687. }
  1688. tc.setVisualNavigation(true);
  1689. setCursor(tc);
  1690. if (m_fakeEnd)
  1691. moveRight();
  1692. }
  1693. void FakeVimHandler::Private::leaveFakeVim()
  1694. {
  1695. // The command might have destroyed the editor.
  1696. if (m_textedit || m_plaintextedit) {
  1697. // We fake vi-style end-of-line behaviour
  1698. m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
  1699. //QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
  1700. // || !atBlockEnd() || block().length() <= 1,
  1701. // qDebug() << "Cursor at EOL after key handler");
  1702. if (m_fakeEnd)
  1703. moveLeft();
  1704. m_oldPosition = position();
  1705. if (hasConfig(ConfigShowMarks))
  1706. updateSelection();
  1707. exportSelection();
  1708. updateCursorShape();
  1709. }
  1710. }
  1711. bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
  1712. {
  1713. const int key = ev->key();
  1714. const int mods = ev->modifiers();
  1715. KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing);
  1716. if (key == Key_Escape) {
  1717. if (m_subsubmode == SearchSubSubMode)
  1718. return true;
  1719. // Not sure this feels good. People often hit Esc several times.
  1720. if (isNoVisualMode()
  1721. && m_mode == CommandMode
  1722. && m_submode == NoSubMode
  1723. && g.currentCommand.isEmpty()
  1724. && g.returnToMode == CommandMode)
  1725. return false;
  1726. return true;
  1727. }
  1728. // We are interested in overriding most Ctrl key combinations.
  1729. if (mods == int(HostOsInfo::controlModifier())
  1730. && !config(ConfigPassControlKey).toBool()
  1731. && ((key >= Key_A && key <= Key_Z && key != Key_K)
  1732. || key == Key_BracketLeft || key == Key_BracketRight)) {
  1733. // Ctrl-K is special as it is the Core's default notion of Locator
  1734. if (m_passing) {
  1735. KEY_DEBUG(" PASSING CTRL KEY");
  1736. // We get called twice on the same key
  1737. //m_passing = false;
  1738. return false;
  1739. }
  1740. KEY_DEBUG(" NOT PASSING CTRL KEY");
  1741. //updateMiniBuffer();
  1742. return true;
  1743. }
  1744. // Let other shortcuts trigger.
  1745. return false;
  1746. }
  1747. EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
  1748. {
  1749. const int key = ev->key();
  1750. const int mods = ev->modifiers();
  1751. if (key == Key_Shift || key == Key_Alt || key == Key_Control
  1752. || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
  1753. {
  1754. KEY_DEBUG("PLAIN MODIFIER");
  1755. return EventUnhandled;
  1756. }
  1757. if (m_passing) {
  1758. passShortcuts(false);
  1759. KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
  1760. //if (input.is(',')) { // use ',,' to leave, too.
  1761. // qDebug() << "FINISHED...";
  1762. // return EventHandled;
  1763. //}
  1764. m_passing = false;
  1765. updateMiniBuffer();
  1766. KEY_DEBUG(" PASS TO CORE");
  1767. return EventPassedToCore;
  1768. }
  1769. bool inSnippetMode = false;
  1770. QMetaObject::invokeMethod(editor(),
  1771. "inSnippetMode", Q_ARG(bool *, &inSnippetMode));
  1772. if (inSnippetMode)
  1773. return EventPassedToCore;
  1774. // Fake "End of line"
  1775. //m_tc = cursor();
  1776. //bool hasBlock = false;
  1777. //emit q->requestHasBlockSelection(&hasBlock);
  1778. //qDebug() << "IMPORT BLOCK 2:" << hasBlock;
  1779. //if (0 && hasBlock) {
  1780. // (pos > anc) ? --pos : --anc;
  1781. //if ((mods & RealControlModifier) != 0) {
  1782. // if (key >= Key_A && key <= Key_Z)
  1783. // key = shift(key); // make it lower case
  1784. // key = control(key);
  1785. //} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
  1786. // key = shift(key);
  1787. //}
  1788. //QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
  1789. // || !atBlockEnd() || block().length() <= 1,
  1790. // qDebug() << "Cursor at EOL before key handler");
  1791. enterFakeVim();
  1792. EventResult result = handleKey(Input(key, mods, ev->text()));
  1793. leaveFakeVim();
  1794. return result;
  1795. }
  1796. void FakeVimHandler::Private::installEventFilter()
  1797. {
  1798. EDITOR(viewport()->installEventFilter(q));
  1799. EDITOR(installEventFilter(q));
  1800. }
  1801. void FakeVimHandler::Private::setupWidget()
  1802. {
  1803. m_mode = CommandMode;
  1804. resetCommandMode();
  1805. if (m_textedit)
  1806. m_textedit->setLineWrapMode(QTextEdit::NoWrap);
  1807. else if (m_plaintextedit)
  1808. m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
  1809. m_wasReadOnly = EDITOR(isReadOnly());
  1810. updateEditor();
  1811. importSelection();
  1812. updateMiniBuffer();
  1813. updateCursorShape();
  1814. recordJump();
  1815. setTargetColumn();
  1816. if (atEndOfLine() && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode)
  1817. moveLeft();
  1818. }
  1819. void FakeVimHandler::Private::exportSelection()
  1820. {
  1821. int pos = position();
  1822. int anc = isVisualMode() ? anchor() : position();
  1823. m_oldInternalPosition = pos;
  1824. m_oldInternalAnchor = anc;
  1825. if (isVisualMode()) {
  1826. bool visualBlockInverted;
  1827. if (m_visualMode == VisualBlockMode) {
  1828. const int col1 = anc - document()->findBlock(anc).position();
  1829. const int col2 = pos - document()->findBlock(pos).position();
  1830. visualBlockInverted = col1 > col2;
  1831. } else {
  1832. visualBlockInverted = anc > pos;
  1833. }
  1834. if (visualBlockInverted)
  1835. setAnchorAndPosition(anc + 1, pos);
  1836. else
  1837. setAnchorAndPosition(anc, pos + 1);
  1838. if (m_visualMode == VisualBlockMode) {
  1839. emit q->requestSetBlockSelection(false);
  1840. emit q->requestSetBlockSelection(true);
  1841. } else if (m_visualMode == VisualLineMode) {
  1842. const int posLine = lineForPosition(pos);
  1843. const int ancLine = lineForPosition(anc);
  1844. if (anc < pos) {
  1845. pos = lastPositionInLine(posLine);
  1846. anc = firstPositionInLine(ancLine);
  1847. } else {
  1848. pos = firstPositionInLine(posLine);
  1849. anc = lastPositionInLine(ancLine);
  1850. }
  1851. // putting cursor on folded line will unfold the line, so move the cursor a bit
  1852. if (!document()->findBlock(pos).isVisible())
  1853. ++pos;
  1854. setAnchorAndPosition(anc, pos);
  1855. } else if (m_visualMode == VisualCharMode) {
  1856. /* Nothing */
  1857. } else {
  1858. QTC_CHECK(false);
  1859. }
  1860. setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position);
  1861. setMark(QLatin1Char('>'), mark(QLatin1Char('>')).position);
  1862. } else {
  1863. if (m_subsubmode == SearchSubSubMode && !m_searchCursor.isNull())
  1864. setCursor(m_searchCursor);
  1865. else
  1866. setAnchorAndPosition(pos, pos);
  1867. }
  1868. m_oldExternalPosition = position();
  1869. m_oldExternalAnchor = anchor();
  1870. }
  1871. void FakeVimHandler::Private::recordInsertion(const QString &insert)
  1872. {
  1873. const int pos = position();
  1874. if (insert.isNull()) {
  1875. const int dist = pos - m_oldPosition;
  1876. if (dist > 0) {
  1877. Range range(m_oldPosition, pos);
  1878. QString text = selectText(range);
  1879. // escape text like <ESC>
  1880. text.replace(_("<"), _("<LT>"));
  1881. m_lastInsertion.append(text);
  1882. } else if (dist < 0) {
  1883. m_lastInsertion.resize(m_lastInsertion.size() + dist);
  1884. }
  1885. } else {
  1886. m_lastInsertion += insert;
  1887. }
  1888. if (m_oldPosition != pos) {
  1889. m_oldPosition = pos;
  1890. setTargetColumn();
  1891. }
  1892. }
  1893. void FakeVimHandler::Private::ensureCursorVisible()
  1894. {
  1895. int pos = position();
  1896. int anc = isVisualMode() ? anchor() : position();
  1897. // fix selection so it is outside folded block
  1898. int start = qMin(pos, anc);
  1899. int end = qMax(pos, anc) + 1;
  1900. QTextBlock block = document()->findBlock(start);
  1901. QTextBlock block2 = document()->findBlock(end);
  1902. if (!block.isVisible() || !block2.isVisible()) {
  1903. // FIXME: Moving cursor left/right or unfolding block immediately after block is folded
  1904. // should restore cursor position inside block.
  1905. // Changing cursor position after folding is not Vim behavior so at least record the jump.
  1906. if (block.isValid() && !block.isVisible())
  1907. recordJump();
  1908. pos = start;
  1909. while (block.isValid() && !block.isVisible())
  1910. block = block.previous();
  1911. if (block.isValid())
  1912. pos = block.position() + qMin(m_targetColumn, block.length() - 2);
  1913. if (isVisualMode()) {
  1914. anc = end;
  1915. while (block2.isValid() && !block2.isVisible()) {
  1916. anc = block2.position() + block2.length() - 2;
  1917. block2 = block2.next();
  1918. }
  1919. }
  1920. setAnchorAndPosition(anc, pos);
  1921. }
  1922. }
  1923. void FakeVimHandler::Private::importSelection()
  1924. {
  1925. bool hasBlock = false;
  1926. emit q->requestHasBlockSelection(&hasBlock);
  1927. if (position() == m_oldExternalPosition
  1928. && anchor() == m_oldExternalAnchor) {
  1929. // Undo drawing correction.
  1930. setAnchorAndPosition(m_oldInternalAnchor, m_oldInternalPosition);
  1931. } else {
  1932. // Import new selection.
  1933. Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
  1934. if (cursor().hasSelection()) {
  1935. if (mods & HostOsInfo::controlModifier())
  1936. m_visualMode = VisualBlockMode;
  1937. else if (mods & Qt::AltModifier)
  1938. m_visualMode = VisualBlockMode;
  1939. else if (mods & Qt::ShiftModifier)
  1940. m_visualMode = VisualLineMode;
  1941. else
  1942. m_visualMode = VisualCharMode;
  1943. m_lastVisualMode = m_visualMode;
  1944. } else {
  1945. m_visualMode = NoVisualMode;
  1946. }
  1947. }
  1948. }
  1949. void FakeVimHandler::Private::updateEditor()
  1950. {
  1951. const int charWidth = QFontMetrics(EDITOR(font())).width(QLatin1Char(' '));
  1952. EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
  1953. setupCharClass();
  1954. }
  1955. void FakeVimHandler::Private::restoreWidget(int tabSize)
  1956. {
  1957. //clearMessage();
  1958. //updateMiniBuffer();
  1959. //EDITOR(removeEventFilter(q));
  1960. //EDITOR(setReadOnly(m_wasReadOnly));
  1961. const int charWidth = QFontMetrics(EDITOR(font())).width(QLatin1Char(' '));
  1962. EDITOR(setTabStopWidth(charWidth * tabSize));
  1963. m_visualMode = NoVisualMode;
  1964. // Force "ordinary" cursor.
  1965. m_mode = InsertMode;
  1966. m_submode = NoSubMode;
  1967. m_subsubmode = NoSubSubMode;
  1968. updateCursorShape();
  1969. updateSelection();
  1970. updateHighlights();
  1971. }
  1972. EventResult FakeVimHandler::Private::handleKey(const Input &input)
  1973. {
  1974. KEY_DEBUG("HANDLE INPUT: " << input << " MODE: " << mode);
  1975. bool handleMapped = true;
  1976. bool hasInput = input.isValid();
  1977. if (hasInput)
  1978. g.pendingInput.append(input);
  1979. // Waiting on input to complete mapping?
  1980. if (g.inputTimer != -1) {
  1981. killTimer(g.inputTimer);
  1982. g.inputTimer = -1;
  1983. // If there is a new input add it to incomplete input or
  1984. // if the mapped input can be completed complete.
  1985. if (hasInput && g.currentMap.walk(input)) {
  1986. if (g.currentMap.canExtend()) {
  1987. g.currentCommand.append(input.toString());
  1988. updateMiniBuffer();
  1989. g.inputTimer = startTimer(1000);
  1990. return EventHandled;
  1991. } else {
  1992. hasInput = false;
  1993. handleMappedKeys();
  1994. }
  1995. } else if (g.currentMap.isComplete()) {
  1996. handleMappedKeys();
  1997. } else {
  1998. g.currentMap.reset();
  1999. handleMapped = false;
  2000. }
  2001. g.currentCommand.clear();
  2002. updateMiniBuffer();
  2003. }
  2004. EventResult r = EventUnhandled;
  2005. while (!g.pendingInput.isEmpty()) {
  2006. const Input &in = g.pendingInput.front();
  2007. // invalid input is used to pop mapping state
  2008. if (!in.isValid()) {
  2009. unhandleMappedKeys();
  2010. } else {
  2011. if (handleMapped && !g.mapStates.last().noremap && m_subsubmode != SearchSubSubMode) {
  2012. if (!g.currentMap.isValid()) {
  2013. g.currentMap.reset(currentModeCode());
  2014. if (!g.currentMap.walk(g.pendingInput) && g.currentMap.isComplete()) {
  2015. handleMappedKeys();
  2016. continue;
  2017. }
  2018. }
  2019. // handle user mapping
  2020. if (g.currentMap.canExtend()) {
  2021. g.currentCommand.append(in.toString());
  2022. updateMiniBuffer();
  2023. // wait for user to press any key or trigger complete mapping after interval
  2024. g.inputTimer = startTimer(1000);
  2025. return EventHandled;
  2026. } else if (g.currentMap.isComplete()) {
  2027. handleMappedKeys();
  2028. continue;
  2029. }
  2030. }
  2031. r = handleDefaultKey(in);
  2032. if (r != EventHandled) {
  2033. // clear bad mapping
  2034. const int index = g.pendingInput.lastIndexOf(Input());
  2035. if (index != -1)
  2036. g.pendingInput.remove(0, index - 1);
  2037. }
  2038. }
  2039. handleMapped = true;
  2040. g.pendingInput.pop_front();
  2041. }
  2042. return r;
  2043. }
  2044. EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input)
  2045. {
  2046. if (input == Nop)
  2047. return EventHandled;
  2048. else if (m_subsubmode == SearchSubSubMode)
  2049. return handleSearchSubSubMode(input);
  2050. else if (m_mode == CommandMode)
  2051. return handleCommandMode(input);
  2052. else if (m_mode == InsertMode)
  2053. return handleInsertMode(input);
  2054. else if (m_mode == ReplaceMode)
  2055. return handleReplaceMode(input);
  2056. else if (m_mode == ExMode)
  2057. return handleExMode(input);
  2058. return EventUnhandled;
  2059. }
  2060. void FakeVimHandler::Private::handleMappedKeys()
  2061. {
  2062. int maxMapDepth = g.mapStates.last().maxMapDepth - 1;
  2063. int invalidCount = g.currentMap.invalidInputCount();
  2064. if (invalidCount > 0) {
  2065. g.mapStates.remove(g.mapStates.size() - invalidCount, invalidCount);
  2066. QTC_CHECK(!g.mapStates.empty());
  2067. for (int i = 0; i < invalidCount; ++i)
  2068. endEditBlock();
  2069. }
  2070. if (maxMapDepth <= 0) {
  2071. showMessage(MessageError, tr("recursive mapping"));
  2072. g.pendingInput.remove(0, g.currentMap.mapLength() + invalidCount);
  2073. } else {
  2074. const Inputs &inputs = g.currentMap.inputs();
  2075. QVector<Input> rest = g.pendingInput.mid(g.currentMap.mapLength() + invalidCount);
  2076. g.pendingInput.clear();
  2077. g.pendingInput << inputs << Input() << rest;
  2078. g.mapStates << MappingState(maxMapDepth, inputs.noremap(), inputs.silent());
  2079. g.commandBuffer.setHistoryAutoSave(false);
  2080. beginLargeEditBlock();
  2081. }
  2082. g.currentMap.reset();
  2083. }
  2084. void FakeVimHandler::Private::unhandleMappedKeys()
  2085. {
  2086. if (g.mapStates.size() == 1)
  2087. return;
  2088. g.mapStates.pop_back();
  2089. endEditBlock();
  2090. if (g.mapStates.size() == 1)
  2091. g.commandBuffer.setHistoryAutoSave(true);
  2092. if (m_mode == ExMode || m_subsubmode == SearchSubSubMode)
  2093. updateMiniBuffer(); // update cursor position on command line
  2094. }
  2095. void FakeVimHandler::Private::timerEvent(QTimerEvent *ev)
  2096. {
  2097. if (ev->timerId() == g.inputTimer) {
  2098. if (g.currentMap.isComplete())
  2099. handleMappedKeys();
  2100. enterFakeVim();
  2101. handleKey(Input());
  2102. leaveFakeVim();
  2103. }
  2104. }
  2105. void FakeVimHandler::Private::stopIncrementalFind()
  2106. {
  2107. if (g.findPending) {
  2108. g.findPending = false;
  2109. QTextCursor tc = cursor();
  2110. setAnchorAndPosition(m_findStartPosition, tc.selectionStart());
  2111. finishMovement();
  2112. setAnchor();
  2113. }
  2114. }
  2115. void FakeVimHandler::Private::updateFind(bool isComplete)
  2116. {
  2117. if (!isComplete && !hasConfig(ConfigIncSearch))
  2118. return;
  2119. g.currentMessage.clear();
  2120. const QString &needle = g.searchBuffer.contents();
  2121. SearchData sd;
  2122. sd.needle = needle;
  2123. sd.forward = g.lastSearchForward;
  2124. sd.highlightMatches = isComplete;
  2125. if (isComplete) {
  2126. setPosition(m_searchStartPosition);
  2127. recordJump();
  2128. }
  2129. search(sd, isComplete);
  2130. }
  2131. bool FakeVimHandler::Private::atEmptyLine(const QTextCursor &tc) const
  2132. {
  2133. if (tc.isNull())
  2134. return atEmptyLine(cursor());
  2135. return tc.block().length() == 1;
  2136. }
  2137. bool FakeVimHandler::Private::atBoundary(bool end, bool simple, bool onlyWords,
  2138. const QTextCursor &tc) const
  2139. {
  2140. if (tc.isNull())
  2141. return atBoundary(end, simple, onlyWords, cursor());
  2142. if (atEmptyLine(tc))
  2143. return true;
  2144. int pos = tc.position();
  2145. QChar c1 = document()->characterAt(pos);
  2146. QChar c2 = document()->characterAt(pos + (end ? 1 : -1));
  2147. int thisClass = charClass(c1, simple);
  2148. return (!onlyWords || thisClass != 0)
  2149. && (c2.isNull() || c2 == ParagraphSeparator || thisClass != charClass(c2, simple));
  2150. }
  2151. bool FakeVimHandler::Private::atWordBoundary(bool end, bool simple, const QTextCursor &tc) const
  2152. {
  2153. return atBoundary(end, simple, true, tc);
  2154. }
  2155. bool FakeVimHandler::Private::atWordStart(bool simple, const QTextCursor &tc) const
  2156. {
  2157. return atWordBoundary(false, simple, tc);
  2158. }
  2159. bool FakeVimHandler::Private::atWordEnd(bool simple, const QTextCursor &tc) const
  2160. {
  2161. return atWordBoundary(true, simple, tc);
  2162. }
  2163. bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
  2164. {
  2165. for (int i = document()->findBlock(pos).position(); i < pos; ++i) {
  2166. if (!document()->characterAt(i).isSpace())
  2167. return false;
  2168. }
  2169. return true;
  2170. }
  2171. void FakeVimHandler::Private::setUndoPosition(bool overwrite)
  2172. {
  2173. const int rev = revision();
  2174. if (!overwrite && !m_undo.empty() && m_undo.top().revision >= rev)
  2175. return;
  2176. int pos = position();
  2177. if (m_mode != InsertMode && m_mode != ReplaceMode) {
  2178. if (isVisualMode() || m_submode == DeleteSubMode) {
  2179. pos = qMin(pos, anchor());
  2180. if (isVisualLineMode())
  2181. pos = firstPositionInLine(lineForPosition(pos));
  2182. } else if (m_movetype == MoveLineWise && hasConfig(ConfigStartOfLine)) {
  2183. QTextCursor tc = cursor();
  2184. if (m_submode == ShiftLeftSubMode || m_submode == ShiftRightSubMode
  2185. || m_submode == IndentSubMode) {
  2186. pos = qMin(pos, anchor());
  2187. }
  2188. tc.setPosition(pos);
  2189. moveToFirstNonBlankOnLine(&tc);
  2190. pos = qMin(pos, tc.position());
  2191. }
  2192. }
  2193. m_redo.clear();
  2194. while (!m_undo.empty() && m_undo.top().revision >= rev)
  2195. m_undo.pop();
  2196. m_lastChangePosition = CursorPosition(document(), pos);
  2197. if (isVisualMode()) {
  2198. setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position);
  2199. setMark(QLatin1Char('>'), mark(QLatin1Char('>')).position);
  2200. }
  2201. m_undo.push(
  2202. State(rev, m_lastChangePosition, m_marks, m_lastVisualMode, m_lastVisualModeInverted));
  2203. }
  2204. void FakeVimHandler::Private::moveDown(int n)
  2205. {
  2206. QTextBlock block = cursor().block();
  2207. const int col = position() - block.position();
  2208. const int targetLine = qMax(0, lineNumber(block) + n);
  2209. block = document()->findBlockByLineNumber(qMax(0, targetLine - 1));
  2210. if (!block.isValid())
  2211. block = document()->lastBlock();
  2212. setPosition(block.position() + qMax(0, qMin(block.length() - 2, col)));
  2213. moveToTargetColumn();
  2214. }
  2215. bool FakeVimHandler::Private::moveToNextParagraph(int count)
  2216. {
  2217. const bool forward = count > 0;
  2218. int repeat = forward ? count : -count;
  2219. QTextBlock block = this->block();
  2220. if (block.isValid() && block.length() == 1)
  2221. ++repeat;
  2222. for (; block.isValid(); block = forward ? block.next() : block.previous()) {
  2223. if (block.length() == 1) {
  2224. if (--repeat == 0)
  2225. break;
  2226. while (block.isValid() && block.length() == 1)
  2227. block = forward ? block.next() : block.previous();
  2228. }
  2229. }
  2230. if (repeat == 0)
  2231. setPosition(block.position());
  2232. else if (repeat == 1)
  2233. setPosition(forward ? lastPositionInDocument() : 0);
  2234. else
  2235. return false;
  2236. setTargetColumn();
  2237. m_movetype = MoveExclusive;
  2238. return true;
  2239. }
  2240. void FakeVimHandler::Private::moveToEndOfLine()
  2241. {
  2242. // Additionally select (in visual mode) or apply current command on hidden lines following
  2243. // the current line.
  2244. bool onlyVisibleLines = isVisualMode() || m_submode != NoSubMode;
  2245. const int id = onlyVisibleLines ? lineNumber(block()) : block().blockNumber() + 1;
  2246. setPosition(lastPositionInLine(id, onlyVisibleLines));
  2247. }
  2248. void FakeVimHandler::Private::moveBehindEndOfLine()
  2249. {
  2250. emit q->fold(1, false);
  2251. int pos = qMin(block().position() + block().length() - 1,
  2252. lastPositionInDocument() + 1);
  2253. setPosition(pos);
  2254. }
  2255. void FakeVimHandler::Private::moveToStartOfLine()
  2256. {
  2257. #if 0
  2258. // does not work for "hidden" documents like in the autotests
  2259. tc.movePosition(StartOfLine, MoveAnchor);
  2260. #else
  2261. setPosition(block().position());
  2262. #endif
  2263. }
  2264. void FakeVimHandler::Private::fixSelection()
  2265. {
  2266. if (m_rangemode == RangeBlockMode)
  2267. return;
  2268. if (m_movetype == MoveExclusive) {
  2269. if (anchor() < position() && atBlockStart()) {
  2270. // Exlusive motion ending at the beginning of line
  2271. // becomes inclusive and end is moved to end of previous line.
  2272. m_movetype = MoveInclusive;
  2273. moveToStartOfLine();
  2274. moveLeft();
  2275. // Exclusive motion ending at the beginning of line and
  2276. // starting at or before first non-blank on a line becomes linewise.
  2277. if (anchor() < block().position() && isFirstNonBlankOnLine(anchor()))
  2278. m_movetype = MoveLineWise;
  2279. }
  2280. }
  2281. if (m_movetype == MoveLineWise)
  2282. m_rangemode = (m_submode == ChangeSubMode)
  2283. ? RangeLineModeExclusive
  2284. : RangeLineMode;
  2285. if (m_movetype == MoveInclusive) {
  2286. if (anchor() <= position()) {
  2287. if (!atBlockEnd())
  2288. setPosition(position() + 1); // correction
  2289. // Omit first character in selection if it's line break on non-empty line.
  2290. int start = anchor();
  2291. int end = position();
  2292. if (afterEndOfLine(document(), start) && start > 0) {
  2293. start = qMin(start + 1, end);
  2294. if (m_submode == DeleteSubMode && !atDocumentEnd())
  2295. setAnchorAndPosition(start, end + 1);
  2296. else
  2297. setAnchorAndPosition(start, end);
  2298. }
  2299. // If more than one line is selected and all are selected completely
  2300. // movement becomes linewise.
  2301. if (start < block().position() && isFirstNonBlankOnLine(start) && atBlockEnd()) {
  2302. if (m_submode != ChangeSubMode) {
  2303. moveRight();
  2304. if (atEmptyLine())
  2305. moveRight();
  2306. }
  2307. m_movetype = MoveLineWise;
  2308. }
  2309. } else {
  2310. setAnchorAndPosition(anchor() + 1, position());
  2311. }
  2312. }
  2313. if (m_positionPastEnd) {
  2314. const int anc = anchor();
  2315. moveBehindEndOfLine();
  2316. moveRight();
  2317. setAnchorAndPosition(anc, position());
  2318. }
  2319. if (m_anchorPastEnd)
  2320. setAnchorAndPosition(anchor() + 1, position());
  2321. }
  2322. void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement, int count)
  2323. {
  2324. finishMovement(dotCommandMovement.arg(count));
  2325. }
  2326. void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
  2327. {
  2328. //dump("FINISH MOVEMENT");
  2329. if (m_submode == FilterSubMode) {
  2330. int beginLine = lineForPosition(anchor());
  2331. int endLine = lineForPosition(position());
  2332. setPosition(qMin(anchor(), position()));
  2333. enterExMode(QString::fromLatin1(".,+%1!").arg(qAbs(endLine - beginLine)));
  2334. return;
  2335. }
  2336. if (m_submode == ChangeSubMode
  2337. || m_submode == DeleteSubMode
  2338. || m_submode == YankSubMode
  2339. || m_submode == InvertCaseSubMode
  2340. || m_submode == DownCaseSubMode
  2341. || m_submode == UpCaseSubMode) {
  2342. if (m_submode != YankSubMode)
  2343. beginEditBlock();
  2344. fixSelection();
  2345. if (m_submode != InvertCaseSubMode
  2346. && m_submode != DownCaseSubMode
  2347. && m_submode != UpCaseSubMode) {
  2348. yankText(currentRange(), m_register);
  2349. if (m_movetype == MoveLineWise)
  2350. setRegister(m_register, registerContents(m_register), RangeLineMode);
  2351. }
  2352. m_positionPastEnd = m_anchorPastEnd = false;
  2353. }
  2354. QString dotCommand;
  2355. if (m_submode == ChangeSubMode) {
  2356. if (m_rangemode != RangeLineModeExclusive)
  2357. setUndoPosition();
  2358. removeText(currentRange());
  2359. dotCommand = _("c");
  2360. if (m_movetype == MoveLineWise)
  2361. insertAutomaticIndentation(true);
  2362. endEditBlock();
  2363. setTargetColumn();
  2364. m_lastInsertion.clear();
  2365. g.returnToMode = InsertMode;
  2366. } else if (m_submode == DeleteSubMode) {
  2367. setUndoPosition();
  2368. const int pos = position();
  2369. // Always delete something (e.g. 'dw' on an empty line deletes the line).
  2370. if (pos == anchor() && m_movetype == MoveInclusive)
  2371. removeText(Range(pos, pos + 1));
  2372. else
  2373. removeText(currentRange());
  2374. dotCommand = _("d");
  2375. if (m_movetype == MoveLineWise)
  2376. handleStartOfLine();
  2377. if (atEndOfLine())
  2378. moveLeft();
  2379. else
  2380. setTargetColumn();
  2381. endEditBlock();
  2382. } else if (m_submode == YankSubMode) {
  2383. const QTextCursor tc = cursor();
  2384. if (m_rangemode == RangeBlockMode) {
  2385. const int pos1 = tc.block().position();
  2386. const int pos2 = document()->findBlock(tc.anchor()).position();
  2387. const int col = qMin(tc.position() - pos1, tc.anchor() - pos2);
  2388. setPosition(qMin(pos1, pos2) + col);
  2389. } else {
  2390. setPosition(qMin(position(), anchor()));
  2391. if (m_rangemode == RangeLineMode) {
  2392. if (isVisualMode())
  2393. moveToStartOfLine();
  2394. }
  2395. }
  2396. leaveVisualMode();
  2397. setTargetColumn();
  2398. } else if (m_submode == InvertCaseSubMode
  2399. || m_submode == UpCaseSubMode
  2400. || m_submode == DownCaseSubMode) {
  2401. if (m_submode == InvertCaseSubMode) {
  2402. invertCase(currentRange());
  2403. dotCommand = QString::fromLatin1("g~");
  2404. } else if (m_submode == DownCaseSubMode) {
  2405. downCase(currentRange());
  2406. dotCommand = QString::fromLatin1("gu");
  2407. } else if (m_submode == UpCaseSubMode) {
  2408. upCase(currentRange());
  2409. dotCommand = QString::fromLatin1("gU");
  2410. }
  2411. if (m_movetype == MoveLineWise)
  2412. handleStartOfLine();
  2413. endEditBlock();
  2414. } else if (m_submode == IndentSubMode
  2415. || m_submode == ShiftRightSubMode
  2416. || m_submode == ShiftLeftSubMode) {
  2417. recordJump();
  2418. setUndoPosition();
  2419. if (m_submode == IndentSubMode) {
  2420. indentSelectedText();
  2421. dotCommand = _("=");
  2422. } else if (m_submode == ShiftRightSubMode) {
  2423. shiftRegionRight(1);
  2424. dotCommand = _(">");
  2425. } else if (m_submode == ShiftLeftSubMode) {
  2426. shiftRegionLeft(1);
  2427. dotCommand = _("<");
  2428. }
  2429. }
  2430. if (!dotCommand.isEmpty() && !dotCommandMovement.isEmpty())
  2431. setDotCommand(dotCommand + dotCommandMovement);
  2432. resetCommandMode();
  2433. }
  2434. void FakeVimHandler::Private::resetCommandMode()
  2435. {
  2436. m_subsubmode = NoSubSubMode;
  2437. m_submode = NoSubMode;
  2438. m_movetype = MoveInclusive;
  2439. m_mvcount.clear();
  2440. m_opcount.clear();
  2441. m_gflag = false;
  2442. m_register = '"';
  2443. //m_tc.clearSelection();
  2444. m_rangemode = RangeCharMode;
  2445. g.currentCommand.clear();
  2446. if (g.returnToMode != CommandMode) {
  2447. const QString lastInsertion = m_lastInsertion;
  2448. if (g.returnToMode == InsertMode)
  2449. enterInsertMode();
  2450. else
  2451. enterReplaceMode();
  2452. m_lastInsertion = lastInsertion;
  2453. moveToTargetColumn();
  2454. }
  2455. if (isNoVisualMode())
  2456. setAnchor();
  2457. }
  2458. void FakeVimHandler::Private::updateSelection()
  2459. {
  2460. QList<QTextEdit::ExtraSelection> selections = m_extraSelections;
  2461. if (hasConfig(ConfigShowMarks)) {
  2462. for (MarksIterator it(m_marks); it.hasNext(); ) {
  2463. it.next();
  2464. QTextEdit::ExtraSelection sel;
  2465. sel.cursor = cursor();
  2466. setCursorPosition(&sel.cursor, it.value().position);
  2467. sel.cursor.setPosition(sel.cursor.position(), MoveAnchor);
  2468. sel.cursor.movePosition(Right, KeepAnchor);
  2469. sel.format = cursor().blockCharFormat();
  2470. sel.format.setForeground(Qt::blue);
  2471. sel.format.setBackground(Qt::green);
  2472. selections.append(sel);
  2473. }
  2474. }
  2475. //qDebug() << "SELECTION: " << selections;
  2476. emit q->selectionChanged(selections);
  2477. }
  2478. void FakeVimHandler::Private::updateHighlights()
  2479. {
  2480. if (!hasConfig(ConfigUseCoreSearch))
  2481. emit q->highlightMatches(m_oldNeedle);
  2482. }
  2483. void FakeVimHandler::Private::updateMiniBuffer()
  2484. {
  2485. if (!m_textedit && !m_plaintextedit)
  2486. return;
  2487. QString msg;
  2488. int cursorPos = -1;
  2489. int anchorPos = -1;
  2490. MessageLevel messageLevel = MessageMode;
  2491. if (g.mapStates.last().silent && g.currentMessageLevel < MessageInfo)
  2492. g.currentMessage.clear();
  2493. if (m_passing) {
  2494. msg = _("PASSING");
  2495. } else if (m_subsubmode == SearchSubSubMode) {
  2496. msg = g.searchBuffer.display();
  2497. if (g.mapStates.size() == 1) {
  2498. cursorPos = g.searchBuffer.cursorPos() + 1;
  2499. anchorPos = g.searchBuffer.anchorPos() + 1;
  2500. }
  2501. } else if (m_mode == ExMode) {
  2502. msg = g.commandBuffer.display();
  2503. if (g.mapStates.size() == 1) {
  2504. cursorPos = g.commandBuffer.cursorPos() + 1;
  2505. anchorPos = g.commandBuffer.anchorPos() + 1;
  2506. }
  2507. } else if (!g.currentMessage.isEmpty()) {
  2508. msg = g.currentMessage;
  2509. g.currentMessage.clear();
  2510. messageLevel = g.currentMessageLevel;
  2511. } else if (g.mapStates.size() > 1 && !g.mapStates.last().silent) {
  2512. // Do not reset previous message when after running a mapped command.
  2513. return;
  2514. } else if (m_mode == CommandMode && !g.currentCommand.isEmpty() && hasConfig(ConfigShowCmd)) {
  2515. msg = g.currentCommand;
  2516. messageLevel = MessageShowCmd;
  2517. } else if (m_mode == CommandMode && isVisualMode()) {
  2518. if (isVisualCharMode())
  2519. msg = _("VISUAL");
  2520. else if (isVisualLineMode())
  2521. msg = _("VISUAL LINE");
  2522. else if (isVisualBlockMode())
  2523. msg = _("VISUAL BLOCK");
  2524. } else if (m_mode == InsertMode) {
  2525. msg = _("INSERT");
  2526. } else if (m_mode == ReplaceMode) {
  2527. msg = _("REPLACE");
  2528. } else {
  2529. QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode);
  2530. if (g.returnToMode == CommandMode)
  2531. msg = _("COMMAND");
  2532. else if (g.returnToMode == InsertMode)
  2533. msg = _("(insert)");
  2534. else
  2535. msg = _("(replace)");
  2536. }
  2537. emit q->commandBufferChanged(msg, cursorPos, anchorPos, messageLevel, q);
  2538. int linesInDoc = linesInDocument();
  2539. int l = cursorLine();
  2540. QString status;
  2541. const QString pos = QString::fromLatin1("%1,%2")
  2542. .arg(l + 1).arg(physicalCursorColumn() + 1);
  2543. // FIXME: physical "-" logical
  2544. if (linesInDoc != 0)
  2545. status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
  2546. else
  2547. status = FakeVimHandler::tr("%1All").arg(pos, -10);
  2548. emit q->statusDataChanged(status);
  2549. }
  2550. void FakeVimHandler::Private::showMessage(MessageLevel level, const QString &msg)
  2551. {
  2552. //qDebug() << "MSG: " << msg;
  2553. g.currentMessage = msg;
  2554. g.currentMessageLevel = level;
  2555. }
  2556. void FakeVimHandler::Private::notImplementedYet()
  2557. {
  2558. qDebug() << "Not implemented in FakeVim";
  2559. showMessage(MessageError, FakeVimHandler::tr("Not implemented in FakeVim"));
  2560. }
  2561. void FakeVimHandler::Private::passShortcuts(bool enable)
  2562. {
  2563. m_passing = enable;
  2564. updateMiniBuffer();
  2565. if (enable)
  2566. QCoreApplication::instance()->installEventFilter(q);
  2567. else
  2568. QCoreApplication::instance()->removeEventFilter(q);
  2569. }
  2570. bool FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
  2571. {
  2572. //const int key = input.key;
  2573. bool handled = true;
  2574. if (m_subsubmode == FtSubSubMode) {
  2575. m_semicolonType = m_subsubdata;
  2576. m_semicolonKey = input.text();
  2577. bool valid = handleFfTt(m_semicolonKey);
  2578. m_subsubmode = NoSubSubMode;
  2579. if (!valid) {
  2580. m_submode = NoSubMode;
  2581. resetCommandMode();
  2582. handled = false;
  2583. } else {
  2584. finishMovement(QString::fromLatin1("%1%2%3")
  2585. .arg(count())
  2586. .arg(m_semicolonType.text())
  2587. .arg(m_semicolonKey));
  2588. }
  2589. } else if (m_subsubmode == TextObjectSubSubMode) {
  2590. bool ok = true;
  2591. if (input.is('w'))
  2592. selectWordTextObject(m_subsubdata.is('i'));
  2593. else if (input.is('W'))
  2594. selectWORDTextObject(m_subsubdata.is('i'));
  2595. else if (input.is('s'))
  2596. selectSentenceTextObject(m_subsubdata.is('i'));
  2597. else if (input.is('p'))
  2598. selectParagraphTextObject(m_subsubdata.is('i'));
  2599. else if (input.is('[') || input.is(']'))
  2600. ok = selectBlockTextObject(m_subsubdata.is('i'), '[', ']');
  2601. else if (input.is('(') || input.is(')') || input.is('b'))
  2602. ok = selectBlockTextObject(m_subsubdata.is('i'), '(', ')');
  2603. else if (input.is('<') || input.is('>'))
  2604. ok = selectBlockTextObject(m_subsubdata.is('i'), '<', '>');
  2605. else if (input.is('{') || input.is('}') || input.is('B'))
  2606. ok = selectBlockTextObject(m_subsubdata.is('i'), '{', '}');
  2607. else if (input.is('"') || input.is('\'') || input.is('`'))
  2608. ok = selectQuotedStringTextObject(m_subsubdata.is('i'), input.asChar());
  2609. else
  2610. ok = false;
  2611. m_subsubmode = NoSubSubMode;
  2612. if (ok) {
  2613. finishMovement(QString::fromLatin1("%1%2%3")
  2614. .arg(count())
  2615. .arg(m_subsubdata.text())
  2616. .arg(input.text()));
  2617. } else {
  2618. resetCommandMode();
  2619. handled = false;
  2620. }
  2621. } else if (m_subsubmode == MarkSubSubMode) {
  2622. setMark(input.asChar(), CursorPosition(cursor()));
  2623. m_subsubmode = NoSubSubMode;
  2624. } else if (m_subsubmode == BackTickSubSubMode
  2625. || m_subsubmode == TickSubSubMode) {
  2626. if (jumpToMark(input.asChar(), m_subsubmode == BackTickSubSubMode)) {
  2627. finishMovement();
  2628. } else {
  2629. resetCommandMode();
  2630. handled = false;
  2631. }
  2632. m_subsubmode = NoSubSubMode;
  2633. } else if (m_subsubmode == ZSubSubMode) {
  2634. handled = false;
  2635. if (input.is('j') || input.is('k')) {
  2636. int pos = position();
  2637. emit q->foldGoTo(input.is('j') ? count() : -count(), false);
  2638. if (pos != position()) {
  2639. handled = true;
  2640. finishMovement(QString::fromLatin1("%1z%2")
  2641. .arg(count())
  2642. .arg(input.text()));
  2643. }
  2644. }
  2645. } else if (m_subsubmode == OpenSquareSubSubMode || CloseSquareSubSubMode) {
  2646. int pos = position();
  2647. if ((input.is('{') && m_subsubmode == OpenSquareSubSubMode))
  2648. searchBalanced(false, QLatin1Char('{'), QLatin1Char('}'));
  2649. else if ((input.is('}') && m_subsubmode == CloseSquareSubSubMode))
  2650. searchBalanced(true, QLatin1Char('}'), QLatin1Char('{'));
  2651. else if ((input.is('(') && m_subsubmode == OpenSquareSubSubMode))
  2652. searchBalanced(false, QLatin1Char('('), QLatin1Char(')'));
  2653. else if ((input.is(')') && m_subsubmode == CloseSquareSubSubMode))
  2654. searchBalanced(true, QLatin1Char(')'), QLatin1Char('('));
  2655. else if (input.is('z'))
  2656. emit q->foldGoTo(m_subsubmode == OpenSquareSubSubMode ? -count() : count(), true);
  2657. handled = pos != position();
  2658. if (handled) {
  2659. finishMovement(QString::fromLatin1("%1%2%3")
  2660. .arg(count())
  2661. .arg(m_subsubmode == OpenSquareSubSubMode ? '[' : ']')
  2662. .arg(input.text()));
  2663. }
  2664. } else {
  2665. handled = false;
  2666. }
  2667. return handled;
  2668. }
  2669. bool FakeVimHandler::Private::handleMovement(const Input &input)
  2670. {
  2671. bool handled = true;
  2672. QString movement;
  2673. int count = this->count();
  2674. if (input.isDigit()) {
  2675. if (input.is('0') && m_mvcount.isEmpty()) {
  2676. m_movetype = MoveExclusive;
  2677. moveToStartOfLine();
  2678. setTargetColumn();
  2679. count = 1;
  2680. } else {
  2681. m_mvcount.append(input.text());
  2682. return true;
  2683. }
  2684. } else if (input.is('a') || input.is('i')) {
  2685. m_subsubmode = TextObjectSubSubMode;
  2686. m_subsubdata = input;
  2687. } else if (input.is('^') || input.is('_')) {
  2688. moveToFirstNonBlankOnLine();
  2689. setTargetColumn();
  2690. m_movetype = MoveExclusive;
  2691. } else if (0 && input.is(',')) {
  2692. // FIXME: fakevim uses ',' by itself, so it is incompatible
  2693. m_subsubmode = FtSubSubMode;
  2694. // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
  2695. //m_subsubdata = m_semicolonType ^ 32;
  2696. handleFfTt(m_semicolonKey);
  2697. m_subsubmode = NoSubSubMode;
  2698. } else if (input.is(';')) {
  2699. m_subsubmode = FtSubSubMode;
  2700. m_subsubdata = m_semicolonType;
  2701. handleFfTt(m_semicolonKey);
  2702. m_subsubmode = NoSubSubMode;
  2703. } else if (input.is('/') || input.is('?')) {
  2704. g.lastSearchForward = input.is('/');
  2705. if (hasConfig(ConfigUseCoreSearch)) {
  2706. // re-use the core dialog.
  2707. g.findPending = true;
  2708. m_findStartPosition = position();
  2709. m_movetype = MoveExclusive;
  2710. setAnchor(); // clear selection: otherwise, search is restricted to selection
  2711. emit q->findRequested(!g.lastSearchForward);
  2712. } else {
  2713. // FIXME: make core find dialog sufficiently flexible to
  2714. // produce the "default vi" behaviour too. For now, roll our own.
  2715. g.currentMessage.clear();
  2716. m_movetype = MoveExclusive;
  2717. m_subsubmode = SearchSubSubMode;
  2718. g.searchBuffer.setPrompt(g.lastSearchForward ? QLatin1Char('/') : QLatin1Char('?'));
  2719. m_searchStartPosition = position();
  2720. m_searchFromScreenLine = firstVisibleLine();
  2721. m_searchCursor = QTextCursor();
  2722. g.searchBuffer.clear();
  2723. }
  2724. } else if (input.is('`')) {
  2725. m_subsubmode = BackTickSubSubMode;
  2726. } else if (input.is('#') || input.is('*')) {
  2727. // FIXME: That's not proper vim behaviour
  2728. QString needle;
  2729. QTextCursor tc = cursor();
  2730. tc.select(QTextCursor::WordUnderCursor);
  2731. needle = QRegExp::escape(tc.selection().toPlainText());
  2732. if (!m_gflag)
  2733. needle = _("\\<") + needle + _("\\>");
  2734. setAnchorAndPosition(tc.position(), tc.anchor());
  2735. g.searchBuffer.historyPush(needle);
  2736. g.lastSearch = needle;
  2737. g.lastSearchForward = input.is('*');
  2738. searchNext();
  2739. } else if (input.is('\'')) {
  2740. m_subsubmode = TickSubSubMode;
  2741. if (m_submode != NoSubMode)
  2742. m_movetype = MoveLineWise;
  2743. } else if (input.is('|')) {
  2744. moveToStartOfLine();
  2745. moveRight(qMin(count, rightDist()) - 1);
  2746. setTargetColumn();
  2747. } else if (input.is('}')) {
  2748. handled = moveToNextParagraph(count);
  2749. } else if (input.is('{')) {
  2750. handled = moveToPreviousParagraph(count);
  2751. } else if (input.isReturn()) {
  2752. moveToStartOfLine();
  2753. moveDown();
  2754. moveToFirstNonBlankOnLine();
  2755. m_movetype = MoveLineWise;
  2756. } else if (input.is('-')) {
  2757. moveToStartOfLine();
  2758. moveUp(count);
  2759. moveToFirstNonBlankOnLine();
  2760. m_movetype = MoveLineWise;
  2761. } else if (input.is('+')) {
  2762. moveToStartOfLine();
  2763. moveDown(count);
  2764. moveToFirstNonBlankOnLine();
  2765. m_movetype = MoveLineWise;
  2766. } else if (input.isKey(Key_Home)) {
  2767. moveToStartOfLine();
  2768. setTargetColumn();
  2769. movement = _("<HOME>");
  2770. } else if (input.is('$') || input.isKey(Key_End)) {
  2771. if (count > 1)
  2772. moveDown(count - 1);
  2773. moveToEndOfLine();
  2774. m_movetype = atEmptyLine() ? MoveExclusive : MoveInclusive;
  2775. setTargetColumn();
  2776. if (m_submode == NoSubMode)
  2777. m_targetColumn = -1;
  2778. if (isVisualMode())
  2779. m_visualTargetColumn = -1;
  2780. movement = _("$");
  2781. } else if (input.is('%')) {
  2782. recordJump();
  2783. if (m_mvcount.isEmpty()) {
  2784. moveToMatchingParanthesis();
  2785. m_movetype = MoveInclusive;
  2786. } else {
  2787. // set cursor position in percentage - formula taken from Vim help
  2788. setPosition(firstPositionInLine((count * linesInDocument() + 99) / 100));
  2789. moveToTargetColumn();
  2790. handleStartOfLine();
  2791. m_movetype = MoveLineWise;
  2792. }
  2793. } else if (input.is('b') || input.isShift(Key_Left)) {
  2794. m_movetype = MoveExclusive;
  2795. moveToNextWordStart(count, false, false);
  2796. setTargetColumn();
  2797. movement = _("b");
  2798. } else if (input.is('B')) {
  2799. m_movetype = MoveExclusive;
  2800. moveToNextWordStart(count, true, false);
  2801. setTargetColumn();
  2802. } else if (input.is('e') && m_gflag) {
  2803. m_movetype = MoveInclusive;
  2804. moveToNextWordEnd(count, false, false);
  2805. setTargetColumn();
  2806. } else if (input.is('e') || input.isShift(Key_Right)) {
  2807. m_movetype = MoveInclusive;
  2808. moveToNextWordEnd(count, false, true, false);
  2809. setTargetColumn();
  2810. movement = _("e");
  2811. } else if (input.is('E') && m_gflag) {
  2812. m_movetype = MoveInclusive;
  2813. moveToNextWordEnd(count, true, false);
  2814. setTargetColumn();
  2815. } else if (input.is('E')) {
  2816. m_movetype = MoveInclusive;
  2817. moveToNextWordEnd(count, true, true, false);
  2818. setTargetColumn();
  2819. } else if (input.isControl('e')) {
  2820. // FIXME: this should use the "scroll" option, and "count"
  2821. if (cursorLineOnScreen() == 0)
  2822. moveDown(1);
  2823. scrollDown(1);
  2824. movement = _("<C-E>");
  2825. } else if (input.is('f')) {
  2826. m_subsubmode = FtSubSubMode;
  2827. m_movetype = MoveInclusive;
  2828. m_subsubdata = input;
  2829. } else if (input.is('F')) {
  2830. m_subsubmode = FtSubSubMode;
  2831. m_movetype = MoveExclusive;
  2832. m_subsubdata = input;
  2833. } else if (!m_gflag && input.is('g')) {
  2834. m_gflag = true;
  2835. return true;
  2836. } else if (input.is('g') || input.is('G')) {
  2837. QString dotCommand = QString::fromLatin1("%1G").arg(count);
  2838. recordJump();
  2839. if (input.is('G') && m_mvcount.isEmpty())
  2840. dotCommand = QString(QLatin1Char('G'));
  2841. int n = (input.is('g')) ? 1 : linesInDocument();
  2842. n = m_mvcount.isEmpty() ? n : count;
  2843. if (m_submode == NoSubMode || m_submode == ZSubMode
  2844. || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
  2845. setPosition(firstPositionInLine(n, false));
  2846. handleStartOfLine();
  2847. } else {
  2848. m_movetype = MoveLineWise;
  2849. m_rangemode = RangeLineMode;
  2850. setAnchor();
  2851. setPosition(firstPositionInLine(n, false));
  2852. }
  2853. setTargetColumn();
  2854. } else if (input.is('h') || input.isKey(Key_Left) || input.isBackspace()) {
  2855. m_movetype = MoveExclusive;
  2856. int n = qMin(count, leftDist());
  2857. if (m_fakeEnd && block().length() > 1)
  2858. ++n;
  2859. moveLeft(n);
  2860. setTargetColumn();
  2861. movement = _("h");
  2862. } else if (input.is('H')) {
  2863. setCursor(EDITOR(cursorForPosition(QPoint(0, 0))));
  2864. moveDown(qMax(count - 1, 0));
  2865. handleStartOfLine();
  2866. } else if (input.is('j') || input.isKey(Key_Down)
  2867. || input.isControl('j') || input.isControl('n')) {
  2868. m_movetype = MoveLineWise;
  2869. moveDown(count);
  2870. movement = _("j");
  2871. } else if (input.is('k') || input.isKey(Key_Up) || input.isControl('p')) {
  2872. m_movetype = MoveLineWise;
  2873. moveUp(count);
  2874. movement = _("k");
  2875. } else if (input.is('l') || input.isKey(Key_Right) || input.is(' ')) {
  2876. m_movetype = MoveExclusive;
  2877. bool pastEnd = count >= rightDist() - 1;
  2878. moveRight(qMax(0, qMin(count, rightDist() - (m_submode == NoSubMode))));
  2879. setTargetColumn();
  2880. if (pastEnd && isVisualMode())
  2881. m_visualTargetColumn = -1;
  2882. } else if (input.is('L')) {
  2883. QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
  2884. setCursor(tc);
  2885. moveUp(qMax(count, 1));
  2886. handleStartOfLine();
  2887. } else if (m_gflag && input.is('m')) {
  2888. moveToStartOfLine();
  2889. moveRight(qMin(columnsOnScreen() / 2, rightDist()) - 1);
  2890. setTargetColumn();
  2891. } else if (input.is('M')) {
  2892. QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
  2893. setCursor(tc);
  2894. handleStartOfLine();
  2895. } else if (input.is('n') || input.is('N')) {
  2896. if (hasConfig(ConfigUseCoreSearch)) {
  2897. bool forward = (input.is('n')) ? g.lastSearchForward : !g.lastSearchForward;
  2898. int pos = position();
  2899. emit q->findNextRequested(!forward);
  2900. if (forward && pos == cursor().selectionStart()) {
  2901. // if cursor is already positioned at the start of a find result, this is returned
  2902. emit q->findNextRequested(false);
  2903. }
  2904. setPosition(cursor().selectionStart());
  2905. } else {
  2906. searchNext(input.is('n'));
  2907. }
  2908. } else if (input.is('t')) {
  2909. m_movetype = MoveInclusive;
  2910. m_subsubmode = FtSubSubMode;
  2911. m_subsubdata = input;
  2912. } else if (input.is('T')) {
  2913. m_movetype = MoveExclusive;
  2914. m_subsubmode = FtSubSubMode;
  2915. m_subsubdata = input;
  2916. } else if (input.is('w') || input.is('W')) { // tested
  2917. // Special case: "cw" and "cW" work the same as "ce" and "cE" if the
  2918. // cursor is on a non-blank - except if the cursor is on the last
  2919. // character of a word: only the current word will be changed
  2920. bool simple = input.is('W');
  2921. if (m_submode == ChangeSubMode) {
  2922. moveToWordEnd(count, simple, true);
  2923. m_movetype = MoveInclusive;
  2924. } else {
  2925. moveToNextWordStart(count, simple, true);
  2926. m_movetype = MoveExclusive;
  2927. }
  2928. setTargetColumn();
  2929. } else if (input.is('z')) {
  2930. m_movetype = MoveLineWise;
  2931. m_subsubmode = ZSubSubMode;
  2932. } else if (input.is('[')) {
  2933. m_subsubmode = OpenSquareSubSubMode;
  2934. } else if (input.is(']')) {
  2935. m_subsubmode = CloseSquareSubSubMode;
  2936. } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
  2937. moveDown(count * (linesOnScreen() - 2) - cursorLineOnScreen());
  2938. scrollToLine(cursorLine());
  2939. handleStartOfLine();
  2940. movement = _("f");
  2941. } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
  2942. moveUp(count * (linesOnScreen() - 2) + cursorLineOnScreen());
  2943. scrollToLine(cursorLine() + linesOnScreen() - 2);
  2944. handleStartOfLine();
  2945. movement = _("b");
  2946. } else if (input.isKey(Key_BracketLeft) || input.isKey(Key_BracketRight)) {
  2947. } else {
  2948. handled = false;
  2949. }
  2950. if (handled && m_subsubmode == NoSubSubMode) {
  2951. if (m_submode == NoSubMode) {
  2952. resetCommandMode();
  2953. } else {
  2954. // finish movement for sub modes
  2955. const QString dotMovement =
  2956. (count > 1 ? QString::number(count) : QString())
  2957. + _(m_gflag ? "g" : "")
  2958. + (movement.isNull() ? QString(input.asChar()) : movement);
  2959. finishMovement(dotMovement);
  2960. setTargetColumn();
  2961. }
  2962. }
  2963. return handled;
  2964. }
  2965. EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
  2966. {
  2967. bool handled = false;
  2968. bool clearGflag = m_gflag;
  2969. bool clearRegister = m_submode != RegisterSubMode;
  2970. bool clearCount = m_submode != RegisterSubMode && !input.isDigit();
  2971. // Process input for a sub-mode.
  2972. if (input.isEscape()) {
  2973. handled = handleEscape();
  2974. } else if (m_subsubmode != NoSubSubMode) {
  2975. handled = handleCommandSubSubMode(input);
  2976. } else if (m_submode == NoSubMode) {
  2977. handled = handleNoSubMode(input);
  2978. } else if (m_submode == ChangeSubMode || m_submode == DeleteSubMode) {
  2979. handled = handleChangeDeleteSubModes(input);
  2980. } else if (m_submode == ReplaceSubMode) {
  2981. handled = handleReplaceSubMode(input);
  2982. } else if (m_submode == FilterSubMode) {
  2983. handled = handleFilterSubMode(input);
  2984. } else if (m_submode == RegisterSubMode) {
  2985. handled = handleRegisterSubMode(input);
  2986. } else if (m_submode == WindowSubMode) {
  2987. handled = handleWindowSubMode(input);
  2988. } else if (m_submode == YankSubMode) {
  2989. handled = handleYankSubMode(input);
  2990. } else if (m_submode == ZSubMode) {
  2991. handled = handleZSubMode(input);
  2992. } else if (m_submode == CapitalZSubMode) {
  2993. handled = handleCapitalZSubMode(input);
  2994. } else if (m_submode == ShiftLeftSubMode
  2995. || m_submode == ShiftRightSubMode
  2996. || m_submode == IndentSubMode) {
  2997. handled = handleShiftSubMode(input);
  2998. } else if (m_submode == InvertCaseSubMode
  2999. || m_submode == DownCaseSubMode
  3000. || m_submode == UpCaseSubMode) {
  3001. handled = handleChangeCaseSubMode(input);
  3002. }
  3003. // Clear state and display incomplete command if necessary.
  3004. if (handled) {
  3005. bool noMode =
  3006. (m_mode == CommandMode && m_submode == NoSubMode && m_subsubmode == NoSubSubMode);
  3007. clearCount = clearCount && noMode && !m_gflag;
  3008. if (clearCount && clearRegister) {
  3009. resetCommandMode();
  3010. } else {
  3011. // Use gflag only for next input.
  3012. if (clearGflag)
  3013. m_gflag = false;
  3014. // Clear [count] and [register] if its no longer needed.
  3015. if (clearCount) {
  3016. m_mvcount.clear();
  3017. m_opcount.clear();
  3018. }
  3019. // Show or clear current command on minibuffer (showcmd).
  3020. if (input.isEscape() || m_mode != CommandMode || clearCount)
  3021. g.currentCommand.clear();
  3022. else
  3023. g.currentCommand.append(input.toString());
  3024. }
  3025. } else {
  3026. resetCommandMode();
  3027. //qDebug() << "IGNORED IN COMMAND MODE: " << key << text
  3028. // << " VISUAL: " << m_visualMode;
  3029. // if a key which produces text was pressed, don't mark it as unhandled
  3030. // - otherwise the text would be inserted while being in command mode
  3031. if (input.text().isEmpty())
  3032. handled = EventUnhandled;
  3033. }
  3034. updateMiniBuffer();
  3035. m_positionPastEnd = (m_visualTargetColumn == -1) && isVisualMode();
  3036. return handled ? EventHandled : EventCancelled;
  3037. }
  3038. bool FakeVimHandler::Private::handleEscape()
  3039. {
  3040. if (isVisualMode())
  3041. leaveVisualMode();
  3042. resetCommandMode();
  3043. return true;
  3044. }
  3045. bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
  3046. {
  3047. bool handled = true;
  3048. if (input.is('&')) {
  3049. handleExCommand(m_gflag ? _("%s//~/&") : _("s"));
  3050. } else if (input.is(':')) {
  3051. enterExMode();
  3052. } else if (input.is('!') && isNoVisualMode()) {
  3053. m_submode = FilterSubMode;
  3054. } else if (input.is('!') && isVisualMode()) {
  3055. enterExMode(QString::fromLatin1("!"));
  3056. } else if (input.is('"')) {
  3057. m_submode = RegisterSubMode;
  3058. } else if (input.is(',')) {
  3059. passShortcuts(true);
  3060. } else if (input.is('.')) {
  3061. //qDebug() << "REPEATING" << quoteUnprintable(g.dotCommand) << count()
  3062. // << input;
  3063. QString savedCommand = g.dotCommand;
  3064. g.dotCommand.clear();
  3065. replay(savedCommand);
  3066. resetCommandMode();
  3067. g.dotCommand = savedCommand;
  3068. } else if (input.is('<') || input.is('>') || input.is('=')) {
  3069. if (isNoVisualMode()) {
  3070. if (input.is('<'))
  3071. m_submode = ShiftLeftSubMode;
  3072. else if (input.is('>'))
  3073. m_submode = ShiftRightSubMode;
  3074. else
  3075. m_submode = IndentSubMode;
  3076. setAnchor();
  3077. } else {
  3078. leaveVisualMode();
  3079. const int lines = qAbs(lineForPosition(position()) - lineForPosition(anchor())) + 1;
  3080. const int repeat = count();
  3081. if (input.is('<'))
  3082. shiftRegionLeft(repeat);
  3083. else if (input.is('>'))
  3084. shiftRegionRight(repeat);
  3085. else
  3086. indentSelectedText();
  3087. const QString selectDotCommand =
  3088. (lines > 1) ? QString::fromLatin1("V%1j").arg(lines - 1): QString();
  3089. setDotCommand(selectDotCommand + QString::fromLatin1("%1%2%2").arg(repeat).arg(input.raw()));
  3090. }
  3091. } else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) {
  3092. if (isVisualBlockMode())
  3093. initVisualBlockInsertMode(QLatin1Char('A'));
  3094. else
  3095. setDotCommand(_("%1a"), count());
  3096. if (!atEndOfLine() && !atEmptyLine())
  3097. moveRight();
  3098. breakEditBlock();
  3099. enterInsertMode();
  3100. setUndoPosition();
  3101. } else if (input.is('A')) {
  3102. breakEditBlock();
  3103. moveBehindEndOfLine();
  3104. setUndoPosition();
  3105. setAnchor();
  3106. enterInsertMode();
  3107. setTargetColumn();
  3108. setDotCommand(_("%1A"), count());
  3109. } else if (input.isControl('a')) {
  3110. if (changeNumberTextObject(count()))
  3111. setDotCommand(_("%1<c-a>"), count());
  3112. } else if ((input.is('c') || input.is('d')) && isNoVisualMode()) {
  3113. setAnchor();
  3114. m_opcount = m_mvcount;
  3115. m_mvcount.clear();
  3116. m_movetype = MoveExclusive;
  3117. m_submode = input.is('c') ? ChangeSubMode : DeleteSubMode;
  3118. } else if ((input.is('c') || input.is('C') || input.is('s') || input.is('R'))
  3119. && (isVisualCharMode() || isVisualLineMode())) {
  3120. setDotCommand(visualDotCommand() + input.asChar());
  3121. if ((input.is('c')|| input.is('s')) && isVisualCharMode()) {
  3122. leaveVisualMode();
  3123. m_rangemode = RangeCharMode;
  3124. } else {
  3125. leaveVisualMode();
  3126. m_rangemode = RangeLineMode;
  3127. // leaveVisualMode() has set this to MoveInclusive for visual character mode
  3128. m_movetype = MoveLineWise;
  3129. }
  3130. m_submode = ChangeSubMode;
  3131. finishMovement();
  3132. } else if (input.is('C')) {
  3133. setAnchor();
  3134. moveToEndOfLine();
  3135. m_submode = ChangeSubMode;
  3136. setDotCommand(QString(QLatin1Char('C')));
  3137. finishMovement();
  3138. } else if (input.isControl('c')) {
  3139. if (isNoVisualMode())
  3140. showMessage(MessageInfo, tr("Type Alt-v,Alt-v to quit FakeVim mode"));
  3141. else
  3142. leaveVisualMode();
  3143. } else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete))
  3144. && isVisualMode()) {
  3145. setUndoPosition();
  3146. setDotCommand(visualDotCommand() + QLatin1Char('x'));
  3147. if (isVisualCharMode()) {
  3148. leaveVisualMode();
  3149. m_submode = DeleteSubMode;
  3150. finishMovement();
  3151. } else if (isVisualLineMode()) {
  3152. leaveVisualMode();
  3153. m_rangemode = RangeLineMode;
  3154. yankText(currentRange(), m_register);
  3155. removeText(currentRange());
  3156. handleStartOfLine();
  3157. } else if (isVisualBlockMode()) {
  3158. leaveVisualMode();
  3159. m_rangemode = RangeBlockMode;
  3160. yankText(currentRange(), m_register);
  3161. removeText(currentRange());
  3162. setPosition(qMin(position(), anchor()));
  3163. }
  3164. } else if (input.is('D') && isNoVisualMode()) {
  3165. setUndoPosition();
  3166. if (atEndOfLine())
  3167. moveLeft();
  3168. m_submode = DeleteSubMode;
  3169. m_movetype = MoveInclusive;
  3170. setAnchorAndPosition(position(), lastPositionInLine(cursorLine() + count()));
  3171. setDotCommand(QString(QLatin1Char('D')));
  3172. finishMovement();
  3173. setTargetColumn();
  3174. } else if ((input.is('D') || input.is('X')) &&
  3175. (isVisualCharMode() || isVisualLineMode())) {
  3176. setDotCommand(visualDotCommand() + QLatin1Char('X'));
  3177. leaveVisualMode();
  3178. m_rangemode = RangeLineMode;
  3179. m_submode = NoSubMode;
  3180. yankText(currentRange(), m_register);
  3181. removeText(currentRange());
  3182. moveToFirstNonBlankOnLine();
  3183. } else if ((input.is('D') || input.is('X')) && isVisualBlockMode()) {
  3184. setDotCommand(visualDotCommand() + QLatin1Char('X'));
  3185. leaveVisualMode();
  3186. m_rangemode = RangeBlockAndTailMode;
  3187. yankText(currentRange(), m_register);
  3188. removeText(currentRange());
  3189. setPosition(qMin(position(), anchor()));
  3190. } else if (input.isControl('d')) {
  3191. int sline = cursorLineOnScreen();
  3192. // FIXME: this should use the "scroll" option, and "count"
  3193. moveDown(linesOnScreen() / 2);
  3194. handleStartOfLine();
  3195. scrollToLine(cursorLine() - sline);
  3196. } else if (!m_gflag && input.is('g')) {
  3197. m_gflag = true;
  3198. } else if (!isVisualMode() && (input.is('i') || input.isKey(Key_Insert))) {
  3199. setDotCommand(_("%1i"), count());
  3200. breakEditBlock();
  3201. enterInsertMode();
  3202. if (atEndOfLine())
  3203. moveLeft();
  3204. } else if (input.is('I')) {
  3205. if (isVisualMode()) {
  3206. initVisualBlockInsertMode(QLatin1Char('I'));
  3207. } else {
  3208. setDotCommand(_("%1I"), count());
  3209. if (m_gflag)
  3210. moveToStartOfLine();
  3211. else
  3212. moveToFirstNonBlankOnLine();
  3213. //m_tc.clearSelection();
  3214. }
  3215. setUndoPosition();
  3216. breakEditBlock();
  3217. enterInsertMode();
  3218. setTargetColumn();
  3219. } else if (input.isControl('i')) {
  3220. jump(count());
  3221. } else if (input.is('J')) {
  3222. setUndoPosition();
  3223. moveBehindEndOfLine();
  3224. beginEditBlock();
  3225. if (m_submode == NoSubMode)
  3226. joinLines(count(), m_gflag);
  3227. endEditBlock();
  3228. setDotCommand(_("%1J"), count());
  3229. } else if (input.isControl('l')) {
  3230. // screen redraw. should not be needed
  3231. } else if (input.is('m')) {
  3232. m_subsubmode = MarkSubSubMode;
  3233. } else if (isVisualMode() && (input.is('o') || input.is('O'))) {
  3234. int pos = position();
  3235. setAnchorAndPosition(pos, anchor());
  3236. std::swap(m_positionPastEnd, m_anchorPastEnd);
  3237. setTargetColumn();
  3238. if (m_positionPastEnd)
  3239. m_visualTargetColumn = -1;
  3240. } else if (input.is('o') || input.is('O')) {
  3241. bool insertAfter = input.is('o');
  3242. setDotCommand(_(insertAfter ? "%1o" : "%1O"), count());
  3243. setUndoPosition();
  3244. enterInsertMode();
  3245. // Insert new line so that command can be repeated [count] times inserting new line
  3246. // each time without unfolding any lines.
  3247. QTextBlock block = cursor().block();
  3248. bool appendLine = false;
  3249. if (insertAfter) {
  3250. const int line = lineNumber(block);
  3251. appendLine = line >= document()->lineCount();
  3252. setPosition(appendLine ? lastPositionInLine(line) : firstPositionInLine(line + 1));
  3253. } else {
  3254. setPosition(block.position());
  3255. }
  3256. beginEditBlock();
  3257. insertText(QString::fromLatin1("\n"));
  3258. if (!appendLine)
  3259. moveUp();
  3260. insertAutomaticIndentation(insertAfter);
  3261. recordInsertion(QString::fromLatin1("\n"));
  3262. setTargetColumn();
  3263. endEditBlock();
  3264. } else if (input.isControl('o')) {
  3265. jump(-count());
  3266. } else if (input.is('p') || input.is('P')) {
  3267. pasteText(input.is('p'));
  3268. setTargetColumn();
  3269. setDotCommand(_("%1p"), count());
  3270. finishMovement();
  3271. } else if (input.is('r')) {
  3272. m_submode = ReplaceSubMode;
  3273. } else if (!isVisualMode() && input.is('R')) {
  3274. setUndoPosition();
  3275. breakEditBlock();
  3276. enterReplaceMode();
  3277. } else if (input.isControl('r')) {
  3278. int repeat = count();
  3279. while (--repeat >= 0)
  3280. redo();
  3281. } else if (input.is('s') && isVisualBlockMode()) {
  3282. m_opcount.clear();
  3283. m_mvcount.clear();
  3284. setUndoPosition();
  3285. beginEditBlock();
  3286. initVisualBlockInsertMode(QLatin1Char('s'));
  3287. endEditBlock();
  3288. enterInsertMode();
  3289. } else if (input.is('s')) {
  3290. setUndoPosition();
  3291. leaveVisualMode();
  3292. if (atEndOfLine())
  3293. moveLeft();
  3294. setAnchor();
  3295. moveRight(qMin(count(), rightDist()));
  3296. setDotCommand(_("%1s"), count());
  3297. m_submode = ChangeSubMode;
  3298. m_movetype = MoveExclusive;
  3299. finishMovement();
  3300. } else if (input.is('S')) {
  3301. m_movetype = MoveLineWise;
  3302. setUndoPosition();
  3303. if (!isVisualMode()) {
  3304. const int line = cursorLine() + 1;
  3305. const int anc = firstPositionInLine(line);
  3306. const int pos = lastPositionInLine(line + count() - 1);
  3307. setAnchorAndPosition(anc, pos);
  3308. }
  3309. setDotCommand(_("%1S"), count());
  3310. m_submode = ChangeSubMode;
  3311. finishMovement();
  3312. } else if (m_gflag && input.is('t')) {
  3313. handleExCommand(_("tabnext"));
  3314. } else if (m_gflag && input.is('T')) {
  3315. handleExCommand(_("tabprev"));
  3316. } else if (input.isControl('t')) {
  3317. handleExCommand(_("pop"));
  3318. } else if (!m_gflag && input.is('u') && !isVisualMode()) {
  3319. int repeat = count();
  3320. while (--repeat >= 0)
  3321. undo();
  3322. } else if (input.isControl('u')) {
  3323. int sline = cursorLineOnScreen();
  3324. // FIXME: this should use the "scroll" option, and "count"
  3325. moveUp(linesOnScreen() / 2);
  3326. handleStartOfLine();
  3327. scrollToLine(cursorLine() - sline);
  3328. } else if (m_gflag && input.is('v')) {
  3329. if (m_lastVisualMode != NoVisualMode) {
  3330. CursorPosition from = mark(QLatin1Char('<')).position;
  3331. CursorPosition to = mark(QLatin1Char('>')).position;
  3332. toggleVisualMode(m_lastVisualMode);
  3333. setCursorPosition(m_lastVisualModeInverted ? to : from);
  3334. setAnchor();
  3335. setCursorPosition(m_lastVisualModeInverted ? from : to);
  3336. }
  3337. } else if (input.is('v')) {
  3338. toggleVisualMode(VisualCharMode);
  3339. } else if (input.is('V')) {
  3340. toggleVisualMode(VisualLineMode);
  3341. } else if (input.isControl('v')) {
  3342. toggleVisualMode(VisualBlockMode);
  3343. } else if (input.isControl('w')) {
  3344. m_submode = WindowSubMode;
  3345. } else if (input.is('x') && isNoVisualMode()) { // = _("dl")
  3346. m_movetype = MoveExclusive;
  3347. m_submode = DeleteSubMode;
  3348. const int n = qMin(count(), rightDist());
  3349. setAnchorAndPosition(position(), position() + n);
  3350. setDotCommand(_("%1x"), count());
  3351. finishMovement();
  3352. } else if (input.isControl('x')) {
  3353. if (changeNumberTextObject(-count()))
  3354. setDotCommand(_("%1<c-x>"), count());
  3355. } else if (input.is('X')) {
  3356. if (leftDist() > 0) {
  3357. setAnchor();
  3358. moveLeft(qMin(count(), leftDist()));
  3359. yankText(currentRange(), m_register);
  3360. removeText(currentRange());
  3361. }
  3362. } else if (input.is('Y') && isNoVisualMode()) {
  3363. handleYankSubMode(Input(QLatin1Char('y')));
  3364. } else if (input.isControl('y')) {
  3365. // FIXME: this should use the "scroll" option, and "count"
  3366. if (cursorLineOnScreen() == linesOnScreen() - 1)
  3367. moveUp(1);
  3368. scrollUp(1);
  3369. } else if (input.is('y') && isNoVisualMode()) {
  3370. setAnchor();
  3371. m_movetype = MoveExclusive;
  3372. m_submode = YankSubMode;
  3373. } else if (input.is('y') && isVisualCharMode()) {
  3374. m_rangemode = RangeCharMode;
  3375. m_movetype = MoveInclusive;
  3376. m_submode = YankSubMode;
  3377. finishMovement();
  3378. } else if ((input.is('y') && isVisualLineMode())
  3379. || (input.is('Y') && isVisualLineMode())
  3380. || (input.is('Y') && isVisualCharMode())) {
  3381. m_rangemode = RangeLineMode;
  3382. m_movetype = MoveLineWise;
  3383. m_submode = YankSubMode;
  3384. finishMovement();
  3385. } else if ((input.is('y') || input.is('Y')) && isVisualBlockMode()) {
  3386. m_rangemode = RangeBlockMode;
  3387. m_movetype = MoveInclusive;
  3388. m_submode = YankSubMode;
  3389. finishMovement();
  3390. } else if (input.is('z')) {
  3391. m_submode = ZSubMode;
  3392. } else if (input.is('Z')) {
  3393. m_submode = CapitalZSubMode;
  3394. } else if ((input.is('~') || input.is('u') || input.is('U'))) {
  3395. m_movetype = MoveExclusive;
  3396. if (isVisualMode()) {
  3397. if (isVisualLineMode())
  3398. m_rangemode = RangeLineMode;
  3399. else if (isVisualBlockMode())
  3400. m_rangemode = RangeBlockMode;
  3401. leaveVisualMode();
  3402. if (input.is('~'))
  3403. m_submode = InvertCaseSubMode;
  3404. else if (input.is('u'))
  3405. m_submode = DownCaseSubMode;
  3406. else if (input.is('U'))
  3407. m_submode = UpCaseSubMode;
  3408. finishMovement();
  3409. } else if (m_gflag) {
  3410. setUndoPosition();
  3411. if (atEndOfLine())
  3412. moveLeft();
  3413. setAnchor();
  3414. if (input.is('~'))
  3415. m_submode = InvertCaseSubMode;
  3416. else if (input.is('u'))
  3417. m_submode = DownCaseSubMode;
  3418. else if (input.is('U'))
  3419. m_submode = UpCaseSubMode;
  3420. } else {
  3421. if (!atEndOfLine()) {
  3422. beginEditBlock();
  3423. setAnchor();
  3424. moveRight(qMin(count(), rightDist()));
  3425. if (input.is('~'))
  3426. invertCase(currentRange());
  3427. else if (input.is('u'))
  3428. downCase(currentRange());
  3429. else if (input.is('U'))
  3430. upCase(currentRange());
  3431. setDotCommand(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
  3432. endEditBlock();
  3433. }
  3434. }
  3435. } else if (input.isKey(Key_Delete)) {
  3436. setAnchor();
  3437. moveRight(qMin(1, rightDist()));
  3438. removeText(currentRange());
  3439. if (atEndOfLine())
  3440. moveLeft();
  3441. } else if (input.isControl(Key_BracketRight)) {
  3442. handleExCommand(_("tag"));
  3443. } else if (handleMovement(input)) {
  3444. // movement handled
  3445. } else {
  3446. handled = false;
  3447. }
  3448. return handled;
  3449. }
  3450. bool FakeVimHandler::Private::handleChangeDeleteSubModes(const Input &input)
  3451. {
  3452. bool handled = false;
  3453. if ((m_submode == ChangeSubMode && input.is('c'))
  3454. || (m_submode == DeleteSubMode && input.is('d'))) {
  3455. m_movetype = MoveLineWise;
  3456. setUndoPosition();
  3457. const int line = cursorLine() + 1;
  3458. const int anc = firstPositionInLine(line);
  3459. const int pos = lastPositionInLine(line + count() - 1);
  3460. setAnchorAndPosition(anc, pos);
  3461. if (m_submode == ChangeSubMode)
  3462. setDotCommand(_("%1cc"), count());
  3463. else
  3464. setDotCommand(_("%1dd"), count());
  3465. finishMovement();
  3466. m_submode = NoSubMode;
  3467. handled = true;
  3468. } else {
  3469. handled = handleMovement(input);
  3470. }
  3471. return handled;
  3472. }
  3473. bool FakeVimHandler::Private::handleReplaceSubMode(const Input &input)
  3474. {
  3475. bool handled = true;
  3476. setDotCommand(visualDotCommand() + QLatin1Char('r') + input.asChar());
  3477. if (isVisualMode()) {
  3478. setUndoPosition();
  3479. if (isVisualLineMode())
  3480. m_rangemode = RangeLineMode;
  3481. else if (isVisualBlockMode())
  3482. m_rangemode = RangeBlockMode;
  3483. else
  3484. m_rangemode = RangeCharMode;
  3485. leaveVisualMode();
  3486. Range range = currentRange();
  3487. if (m_rangemode == RangeCharMode)
  3488. ++range.endPos;
  3489. Transformation tr =
  3490. &FakeVimHandler::Private::replaceByCharTransform;
  3491. transformText(range, tr, input.asChar());
  3492. } else if (count() <= rightDist()) {
  3493. setUndoPosition();
  3494. setAnchor();
  3495. moveRight(count());
  3496. Range range = currentRange();
  3497. if (input.isReturn()) {
  3498. beginEditBlock();
  3499. replaceText(range, QString());
  3500. insertText(QString::fromLatin1("\n"));
  3501. endEditBlock();
  3502. } else {
  3503. replaceText(range, QString(count(), input.asChar()));
  3504. moveRight(count() - 1);
  3505. }
  3506. setTargetColumn();
  3507. setDotCommand(_("%1r") + input.text(), count());
  3508. } else {
  3509. handled = false;
  3510. }
  3511. m_submode = NoSubMode;
  3512. finishMovement();
  3513. return handled;
  3514. }
  3515. bool FakeVimHandler::Private::handleFilterSubMode(const Input &input)
  3516. {
  3517. return handleMovement(input);
  3518. }
  3519. bool FakeVimHandler::Private::handleRegisterSubMode(const Input &input)
  3520. {
  3521. bool handled = false;
  3522. QChar reg = input.asChar();
  3523. if (QString::fromLatin1("*+.%#:-\"").contains(reg) || reg.isLetterOrNumber()) {
  3524. m_register = reg.unicode();
  3525. m_rangemode = RangeLineMode;
  3526. handled = true;
  3527. }
  3528. m_submode = NoSubMode;
  3529. return handled;
  3530. }
  3531. bool FakeVimHandler::Private::handleShiftSubMode(const Input &input)
  3532. {
  3533. bool handled = false;
  3534. if ((m_submode == ShiftLeftSubMode && input.is('<'))
  3535. || (m_submode == ShiftRightSubMode && input.is('>'))
  3536. || (m_submode == IndentSubMode && input.is('='))) {
  3537. m_movetype = MoveLineWise;
  3538. setUndoPosition();
  3539. moveDown(count() - 1);
  3540. setDotCommand(QString::fromLatin1("%2%1%1").arg(input.asChar()), count());
  3541. finishMovement();
  3542. handled = true;
  3543. m_submode = NoSubMode;
  3544. } else {
  3545. handled = handleMovement(input);
  3546. }
  3547. return handled;
  3548. }
  3549. bool FakeVimHandler::Private::handleChangeCaseSubMode(const Input &input)
  3550. {
  3551. bool handled = false;
  3552. if ((m_submode == InvertCaseSubMode && input.is('~'))
  3553. || (m_submode == DownCaseSubMode && input.is('u'))
  3554. || (m_submode == UpCaseSubMode && input.is('U'))) {
  3555. if (!isFirstNonBlankOnLine(position())) {
  3556. moveToStartOfLine();
  3557. moveToFirstNonBlankOnLine();
  3558. }
  3559. setTargetColumn();
  3560. setUndoPosition();
  3561. setAnchor();
  3562. setPosition(lastPositionInLine(cursorLine() + count()) + 1);
  3563. finishMovement(QString::fromLatin1("%1%2").arg(count()).arg(input.raw()));
  3564. handled = true;
  3565. m_submode = NoSubMode;
  3566. } else {
  3567. handled = handleMovement(input);
  3568. }
  3569. return handled;
  3570. }
  3571. bool FakeVimHandler::Private::handleWindowSubMode(const Input &input)
  3572. {
  3573. emit q->windowCommandRequested(input.key());
  3574. m_submode = NoSubMode;
  3575. return EventHandled;
  3576. }
  3577. bool FakeVimHandler::Private::handleYankSubMode(const Input &input)
  3578. {
  3579. bool handled = false;
  3580. if (input.is('y')) {
  3581. m_movetype = MoveLineWise;
  3582. int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
  3583. Range range(position(), endPos, RangeLineMode);
  3584. yankText(range);
  3585. m_submode = NoSubMode;
  3586. handled = true;
  3587. } else {
  3588. handled = handleMovement(input);
  3589. }
  3590. return handled;
  3591. }
  3592. bool FakeVimHandler::Private::handleZSubMode(const Input &input)
  3593. {
  3594. bool handled = true;
  3595. bool foldMaybeClosed = false;
  3596. if (input.isReturn() || input.is('t')
  3597. || input.is('-') || input.is('b')
  3598. || input.is('.') || input.is('z')) {
  3599. // Cursor line to top/center/bottom of window.
  3600. Qt::AlignmentFlag align;
  3601. if (input.isReturn() || input.is('t'))
  3602. align = Qt::AlignTop;
  3603. else if (input.is('.') || input.is('z'))
  3604. align = Qt::AlignBottom;
  3605. else
  3606. align = Qt::AlignVCenter;
  3607. const bool moveToNonBlank = (input.is('.') || input.isReturn() || input.is('-'));
  3608. const int line = m_mvcount.isEmpty() ? -1 : firstPositionInLine(count());
  3609. alignViewportToCursor(align, line, moveToNonBlank);
  3610. } else if (input.is('o') || input.is('c')) {
  3611. // Open/close current fold.
  3612. foldMaybeClosed = input.is('c');
  3613. emit q->fold(count(), foldMaybeClosed);
  3614. } else if (input.is('O') || input.is('C')) {
  3615. // Recursively open/close current fold.
  3616. foldMaybeClosed = input.is('C');
  3617. emit q->fold(-1, foldMaybeClosed);
  3618. } else if (input.is('a') || input.is('A')) {
  3619. // Toggle current fold.
  3620. foldMaybeClosed = true;
  3621. emit q->foldToggle(input.is('a') ? count() : -1);
  3622. } else if (input.is('R') || input.is('M')) {
  3623. // Open/close all folds in document.
  3624. foldMaybeClosed = input.is('M');
  3625. emit q->foldAll(foldMaybeClosed);
  3626. } else if (input.is('j') || input.is('k')) {
  3627. emit q->foldGoTo(input.is('j') ? count() : -count(), false);
  3628. } else {
  3629. handled = false;
  3630. }
  3631. if (foldMaybeClosed)
  3632. ensureCursorVisible();
  3633. m_submode = NoSubMode;
  3634. return handled;
  3635. }
  3636. bool FakeVimHandler::Private::handleCapitalZSubMode(const Input &input)
  3637. {
  3638. // Recognize ZZ and ZQ as aliases for ":x" and ":q!".
  3639. bool handled = true;
  3640. if (input.is('Z'))
  3641. handleExCommand(QString(QLatin1Char('x')));
  3642. else if (input.is('Q'))
  3643. handleExCommand(_("q!"));
  3644. else
  3645. handled = false;
  3646. m_submode = NoSubMode;
  3647. return handled;
  3648. }
  3649. EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
  3650. {
  3651. bool clearLastInsertion = m_breakEditBlock;
  3652. if (m_oldPosition != position()) {
  3653. if (clearLastInsertion) {
  3654. clearLastInsertion = false;
  3655. m_lastInsertion = _("<INSERT>");
  3656. }
  3657. recordInsertion();
  3658. }
  3659. if (input.isEscape()) {
  3660. moveLeft(qMin(1, leftDist()));
  3661. enterCommandMode();
  3662. g.dotCommand += m_lastInsertion;
  3663. g.dotCommand += QChar(27);
  3664. } else if (input.isKey(Key_Left)) {
  3665. breakEditBlock();
  3666. moveLeft(1);
  3667. setTargetColumn();
  3668. } else if (input.isKey(Key_Right)) {
  3669. breakEditBlock();
  3670. moveRight(1);
  3671. setTargetColumn();
  3672. } else if (input.isKey(Key_Up)) {
  3673. breakEditBlock();
  3674. moveUp(1);
  3675. } else if (input.isKey(Key_Down)) {
  3676. breakEditBlock();
  3677. moveDown(1);
  3678. } else if (input.isKey(Key_Insert)) {
  3679. m_mode = InsertMode;
  3680. recordInsertion(_("<INSERT>"));
  3681. } else if (input.isControl('o')) {
  3682. enterCommandMode(ReplaceMode);
  3683. } else {
  3684. if (clearLastInsertion)
  3685. m_lastInsertion = _("<INSERT>");
  3686. joinPreviousEditBlock();
  3687. if (!atEndOfLine()) {
  3688. setAnchor();
  3689. moveRight();
  3690. removeText(currentRange());
  3691. }
  3692. const QString text = input.text();
  3693. setAnchor();
  3694. insertText(text);
  3695. endEditBlock();
  3696. recordInsertion();
  3697. }
  3698. m_oldPosition = position();
  3699. updateMiniBuffer();
  3700. return EventHandled;
  3701. }
  3702. EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
  3703. {
  3704. bool clearLastInsertion = m_breakEditBlock;
  3705. if (m_oldPosition != position()) {
  3706. if (clearLastInsertion) {
  3707. clearLastInsertion = false;
  3708. m_lastInsertion.clear();
  3709. }
  3710. recordInsertion();
  3711. }
  3712. QString insert;
  3713. bool move = false;
  3714. if (input.isEscape()) {
  3715. // Repeat insertion [count] times.
  3716. // One instance was already physically inserted while typing.
  3717. const QString text = m_lastInsertion;
  3718. const int repeat = count();
  3719. m_lastInsertion.clear();
  3720. joinPreviousEditBlock();
  3721. replay(text.repeated(repeat - 1));
  3722. if (m_visualBlockInsert && !text.contains(QLatin1Char('\n'))) {
  3723. const CursorPosition lastAnchor = mark(QLatin1Char('<')).position;
  3724. const CursorPosition lastPosition = mark(QLatin1Char('>')).position;
  3725. CursorPosition startPos(lastAnchor.line, qMin(lastPosition.column, lastAnchor.column));
  3726. CursorPosition pos = startPos;
  3727. if (g.dotCommand.endsWith(QLatin1Char('A')))
  3728. pos.column = qMax(lastPosition.column, lastAnchor.column) + 1;
  3729. while (pos.line < lastPosition.line) {
  3730. ++pos.line;
  3731. QTextCursor tc = cursor();
  3732. setCursorPosition(&tc, pos);
  3733. if (pos.line != tc.blockNumber())
  3734. break;
  3735. setCursor(tc);
  3736. if (tc.positionInBlock() == pos.column)
  3737. replay(text.repeated(repeat));
  3738. }
  3739. setCursorPosition(startPos);
  3740. } else {
  3741. moveLeft(qMin(1, leftDist()));
  3742. leaveVisualMode(); // TODO: Remove! Should not be requiered here!
  3743. }
  3744. endEditBlock();
  3745. breakEditBlock();
  3746. m_lastInsertion = text;
  3747. // If command is 'o' or 'O' don't include the first line feed in dot command.
  3748. if (g.dotCommand.endsWith(QLatin1Char('o'), Qt::CaseInsensitive))
  3749. m_lastInsertion.remove(0, 1);
  3750. g.dotCommand += m_lastInsertion + _("<ESC>");
  3751. enterCommandMode();
  3752. m_ctrlVActive = false;
  3753. m_visualBlockInsert = false;
  3754. } else if (m_ctrlVActive) {
  3755. insertInInsertMode(input.raw());
  3756. } else if (input.isControl('o')) {
  3757. enterCommandMode(InsertMode);
  3758. } else if (input.isControl('v')) {
  3759. m_ctrlVActive = true;
  3760. insert = _("<C-V>");
  3761. } else if (input.isControl('w')) {
  3762. const int blockNumber = cursor().blockNumber();
  3763. const int endPos = position();
  3764. moveToNextWordStart(count(), false, false);
  3765. if (blockNumber != cursor().blockNumber())
  3766. moveToEndOfLine();
  3767. const int beginPos = position();
  3768. Range range(beginPos, endPos, RangeCharMode);
  3769. removeText(range);
  3770. insert = _("<C-W>");
  3771. } else if (input.isKey(Key_Insert)) {
  3772. m_mode = ReplaceMode;
  3773. insert = _("<INSERT>");
  3774. } else if (input.isKey(Key_Left)) {
  3775. moveLeft(count());
  3776. move = true;
  3777. setTargetColumn();
  3778. } else if (input.isControl(Key_Left)) {
  3779. moveToNextWordStart(count(), false, false);
  3780. move = true;
  3781. setTargetColumn();
  3782. } else if (input.isKey(Key_Down)) {
  3783. //removeAutomaticIndentation();
  3784. m_submode = NoSubMode;
  3785. moveDown(count());
  3786. move = true;
  3787. } else if (input.isKey(Key_Up)) {
  3788. //removeAutomaticIndentation();
  3789. m_submode = NoSubMode;
  3790. moveUp(count());
  3791. move = true;
  3792. } else if (input.isKey(Key_Right)) {
  3793. moveRight(count());
  3794. move = true;
  3795. setTargetColumn();
  3796. } else if (input.isControl(Key_Right)) {
  3797. moveToNextWordStart(count(), false, true);
  3798. moveRight(); // we need one more move since we are in insert mode
  3799. move = true;
  3800. setTargetColumn();
  3801. } else if (input.isKey(Key_Home)) {
  3802. moveToStartOfLine();
  3803. move = true;
  3804. setTargetColumn();
  3805. } else if (input.isKey(Key_End)) {
  3806. if (count() > 1)
  3807. moveDown(count() - 1);
  3808. moveBehindEndOfLine();
  3809. move = true;
  3810. setTargetColumn();
  3811. } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) {
  3812. joinPreviousEditBlock();
  3813. m_submode = NoSubMode;
  3814. insertText(QString::fromLatin1("\n"));
  3815. insert = _("\n");
  3816. insertAutomaticIndentation(true);
  3817. endEditBlock();
  3818. } else if (input.isBackspace()) {
  3819. joinPreviousEditBlock();
  3820. m_justAutoIndented = 0;
  3821. if (!m_lastInsertion.isEmpty()
  3822. || hasConfig(ConfigBackspace, "start")
  3823. || hasConfig(ConfigBackspace, "2")) {
  3824. const int line = cursorLine() + 1;
  3825. const Column col = cursorColumn();
  3826. QString data = lineContents(line);
  3827. const Column ind = indentation(data);
  3828. if (col.logical <= ind.logical && col.logical
  3829. && startsWithWhitespace(data, col.physical)) {
  3830. const int ts = config(ConfigTabStop).toInt();
  3831. const int newl = col.logical - 1 - (col.logical - 1) % ts;
  3832. const QString prefix = tabExpand(newl);
  3833. setLineContents(line, prefix + data.mid(col.physical));
  3834. moveToStartOfLine();
  3835. moveRight(prefix.size());
  3836. } else {
  3837. setAnchor();
  3838. cursor().deletePreviousChar();
  3839. }
  3840. }
  3841. insert = _("<BS>");
  3842. endEditBlock();
  3843. } else if (input.isKey(Key_Delete)) {
  3844. setAnchor();
  3845. cursor().deleteChar();
  3846. insert = _("<DELETE>");
  3847. } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
  3848. removeAutomaticIndentation();
  3849. moveDown(count() * (linesOnScreen() - 2));
  3850. move = true;
  3851. } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
  3852. removeAutomaticIndentation();
  3853. moveUp(count() * (linesOnScreen() - 2));
  3854. move = true;
  3855. } else if (input.isKey(Key_Tab)) {
  3856. m_justAutoIndented = 0;
  3857. if (hasConfig(ConfigExpandTab)) {
  3858. const int ts = config(ConfigTabStop).toInt();
  3859. const int col = logicalCursorColumn();
  3860. QString str = QString(ts - col % ts, QLatin1Char(' '));
  3861. m_lastInsertion.append(str);
  3862. insertText(str);
  3863. } else {
  3864. insertInInsertMode(input.raw());
  3865. }
  3866. insert = _("\t");
  3867. } else if (input.isControl('d')) {
  3868. // remove one level of indentation from the current line
  3869. int shift = config(ConfigShiftWidth).toInt();
  3870. int tab = config(ConfigTabStop).toInt();
  3871. int line = cursorLine() + 1;
  3872. int pos = firstPositionInLine(line);
  3873. QString text = lineContents(line);
  3874. int amount = 0;
  3875. int i = 0;
  3876. for (; i < text.size() && amount < shift; ++i) {
  3877. if (text.at(i) == QLatin1Char(' '))
  3878. ++amount;
  3879. else if (text.at(i) == QLatin1Char('\t'))
  3880. amount += tab; // FIXME: take position into consideration
  3881. else
  3882. break;
  3883. }
  3884. removeText(Range(pos, pos+i));
  3885. insert = _("<C-D>");
  3886. //} else if (key >= control('a') && key <= control('z')) {
  3887. // // ignore these
  3888. } else if (input.isControl('p') || input.isControl('n')) {
  3889. QTextCursor tc = cursor();
  3890. moveToNextWordStart(count(), false, false);
  3891. QString str = selectText(Range(position(), tc.position()));
  3892. setCursor(tc);
  3893. emit q->simpleCompletionRequested(str, input.isControl('n'));
  3894. if (input.isControl('p'))
  3895. insert = _("<C-P>");
  3896. else
  3897. insert = _("<C-N>");
  3898. } else if (!input.text().isEmpty()) {
  3899. insert = input.text();
  3900. insertInInsertMode(insert);
  3901. insert.replace(_("<"), _("<LT>"));
  3902. } else {
  3903. // We don't want fancy stuff in insert mode.
  3904. return EventHandled;
  3905. }
  3906. if (move) {
  3907. breakEditBlock();
  3908. m_oldPosition = position();
  3909. } else {
  3910. if (clearLastInsertion)
  3911. m_lastInsertion.clear();
  3912. recordInsertion(insert);
  3913. }
  3914. updateMiniBuffer();
  3915. return EventHandled;
  3916. }
  3917. void FakeVimHandler::Private::insertInInsertMode(const QString &text)
  3918. {
  3919. joinPreviousEditBlock();
  3920. m_justAutoIndented = 0;
  3921. insertText(text);
  3922. if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
  3923. const QString leftText = block().text()
  3924. .left(position() - 1 - block().position());
  3925. if (leftText.simplified().isEmpty()) {
  3926. Range range(position(), position(), m_rangemode);
  3927. indentText(range, text.at(0));
  3928. }
  3929. }
  3930. setTargetColumn();
  3931. endEditBlock();
  3932. m_ctrlVActive = false;
  3933. }
  3934. EventResult FakeVimHandler::Private::handleExMode(const Input &input)
  3935. {
  3936. if (input.isEscape()) {
  3937. g.commandBuffer.clear();
  3938. enterCommandMode(g.returnToMode);
  3939. resetCommandMode();
  3940. m_ctrlVActive = false;
  3941. } else if (m_ctrlVActive) {
  3942. g.commandBuffer.insertChar(input.raw());
  3943. m_ctrlVActive = false;
  3944. } else if (input.isControl('v')) {
  3945. m_ctrlVActive = true;
  3946. return EventHandled;
  3947. } else if (input.isBackspace()) {
  3948. if (g.commandBuffer.isEmpty()) {
  3949. enterCommandMode(g.returnToMode);
  3950. resetCommandMode();
  3951. } else if (g.commandBuffer.hasSelection()) {
  3952. g.commandBuffer.deleteSelected();
  3953. } else {
  3954. g.commandBuffer.deleteChar();
  3955. }
  3956. } else if (input.isKey(Key_Tab)) {
  3957. // FIXME: Complete actual commands.
  3958. g.commandBuffer.historyUp();
  3959. } else if (input.isReturn()) {
  3960. showMessage(MessageCommand, g.commandBuffer.display());
  3961. handleExCommand(g.commandBuffer.contents());
  3962. g.commandBuffer.clear();
  3963. if (m_textedit || m_plaintextedit)
  3964. leaveVisualMode();
  3965. } else if (!g.commandBuffer.handleInput(input)) {
  3966. qDebug() << "IGNORED IN EX-MODE: " << input.key() << input.text();
  3967. return EventUnhandled;
  3968. }
  3969. updateMiniBuffer();
  3970. return EventHandled;
  3971. }
  3972. EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
  3973. {
  3974. EventResult handled = EventHandled;
  3975. if (input.isEscape()) {
  3976. g.currentMessage.clear();
  3977. g.searchBuffer.clear();
  3978. setAnchorAndPosition(m_searchStartPosition, m_searchStartPosition);
  3979. scrollToLine(m_searchFromScreenLine);
  3980. enterCommandMode(g.returnToMode);
  3981. resetCommandMode();
  3982. } else if (input.isBackspace()) {
  3983. if (g.searchBuffer.isEmpty())
  3984. resetCommandMode();
  3985. else
  3986. g.searchBuffer.deleteChar();
  3987. } else if (input.isReturn()) {
  3988. const QString &needle = g.searchBuffer.contents();
  3989. if (!needle.isEmpty())
  3990. g.lastSearch = needle;
  3991. else
  3992. g.searchBuffer.setContents(g.lastSearch);
  3993. if (!g.lastSearch.isEmpty()) {
  3994. updateFind(true);
  3995. finishMovement(g.searchBuffer.prompt() + g.lastSearch + QLatin1Char('\n'));
  3996. } else {
  3997. finishMovement();
  3998. }
  3999. if (g.currentMessage.isEmpty())
  4000. showMessage(MessageCommand, g.searchBuffer.display());
  4001. else
  4002. handled = EventCancelled;
  4003. enterCommandMode(g.returnToMode);
  4004. resetCommandMode();
  4005. g.searchBuffer.clear();
  4006. } else if (input.isKey(Key_Tab)) {
  4007. g.searchBuffer.insertChar(QChar(9));
  4008. } else if (!g.searchBuffer.handleInput(input)) {
  4009. //qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
  4010. return EventUnhandled;
  4011. }
  4012. updateMiniBuffer();
  4013. if (!input.isReturn() && !input.isEscape())
  4014. updateFind(false);
  4015. return handled;
  4016. }
  4017. // This uses 0 based line counting (hidden lines included).
  4018. int FakeVimHandler::Private::parseLineAddress(QString *cmd)
  4019. {
  4020. //qDebug() << "CMD: " << cmd;
  4021. if (cmd->isEmpty())
  4022. return -1;
  4023. int result = -1;
  4024. QChar c = cmd->at(0);
  4025. if (c == QLatin1Char('.')) { // current line
  4026. result = cursorBlockNumber();
  4027. cmd->remove(0, 1);
  4028. } else if (c == QLatin1Char('$')) { // last line
  4029. result = document()->blockCount() - 1;
  4030. cmd->remove(0, 1);
  4031. } else if (c == QLatin1Char('\'')) { // mark
  4032. cmd->remove(0, 1);
  4033. if (cmd->isEmpty()) {
  4034. showMessage(MessageError, msgMarkNotSet(QString()));
  4035. return -1;
  4036. }
  4037. c = cmd->at(0);
  4038. Mark m = mark(c);
  4039. if (!m.isValid() || !m.isLocal(m_currentFileName)) {
  4040. showMessage(MessageError, msgMarkNotSet(c));
  4041. return -1;
  4042. }
  4043. cmd->remove(0, 1);
  4044. result = m.position.line;
  4045. } else if (c.isDigit()) { // line with given number
  4046. result = 0;
  4047. } else if (c == QLatin1Char('-') || c == QLatin1Char('+')) { // add or subtract from current line number
  4048. result = cursorBlockNumber();
  4049. } else if (c == QLatin1Char('/') || c == QLatin1Char('?')
  4050. || (c == QLatin1Char('\\') && cmd->size() > 1 && QString::fromLatin1("/?&").contains(cmd->at(1)))) {
  4051. // search for expression
  4052. SearchData sd;
  4053. if (c == QLatin1Char('/') || c == QLatin1Char('?')) {
  4054. const int end = findUnescaped(c, *cmd, 1);
  4055. if (end == -1)
  4056. return -1;
  4057. sd.needle = cmd->mid(1, end - 1);
  4058. cmd->remove(0, end + 1);
  4059. } else {
  4060. c = cmd->at(1);
  4061. cmd->remove(0, 2);
  4062. sd.needle = (c == QLatin1Char('&')) ? g.lastSubstitutePattern : g.lastSearch;
  4063. }
  4064. sd.forward = (c != QLatin1Char('?'));
  4065. const QTextBlock b = block();
  4066. const int pos = b.position() + (sd.forward ? b.length() - 1 : 0);
  4067. QTextCursor tc = search(sd, pos, 1, true);
  4068. g.lastSearch = sd.needle;
  4069. if (tc.isNull())
  4070. return -1;
  4071. result = tc.block().blockNumber();
  4072. } else {
  4073. return cursorBlockNumber();
  4074. }
  4075. // basic arithmetic ("-3+5" or "++" means "+2" etc.)
  4076. int n = 0;
  4077. bool add = true;
  4078. int i = 0;
  4079. for (; i < cmd->size(); ++i) {
  4080. c = cmd->at(i);
  4081. if (c == QLatin1Char('-') || c == QLatin1Char('+')) {
  4082. if (n != 0)
  4083. result = result + (add ? n - 1 : -(n - 1));
  4084. add = c == QLatin1Char('+');
  4085. result = result + (add ? 1 : -1);
  4086. n = 0;
  4087. } else if (c.isDigit()) {
  4088. n = n * 10 + c.digitValue();
  4089. } else if (!c.isSpace()) {
  4090. break;
  4091. }
  4092. }
  4093. if (n != 0)
  4094. result = result + (add ? n - 1 : -(n - 1));
  4095. *cmd = cmd->mid(i).trimmed();
  4096. return result;
  4097. }
  4098. void FakeVimHandler::Private::setCurrentRange(const Range &range)
  4099. {
  4100. setAnchorAndPosition(range.beginPos, range.endPos);
  4101. m_rangemode = range.rangemode;
  4102. }
  4103. bool FakeVimHandler::Private::parseExCommmand(QString *line, ExCommand *cmd)
  4104. {
  4105. *cmd = ExCommand();
  4106. if (line->isEmpty())
  4107. return false;
  4108. // remove leading colons and spaces
  4109. line->remove(QRegExp(_("^\\s*(:+\\s*)*")));
  4110. // parse range first
  4111. if (!parseLineRange(line, cmd))
  4112. return false;
  4113. // get first command from command line
  4114. QChar close;
  4115. bool subst = false;
  4116. int i = 0;
  4117. for (; i < line->size(); ++i) {
  4118. const QChar &c = line->at(i);
  4119. if (c == QLatin1Char('\\')) {
  4120. ++i; // skip escaped character
  4121. } else if (close.isNull()) {
  4122. if (c == QLatin1Char('|')) {
  4123. // split on |
  4124. break;
  4125. } else if (c == QLatin1Char('/')) {
  4126. subst = i > 0 && (line->at(i - 1) == QLatin1Char('s'));
  4127. close = c;
  4128. } else if (c == QLatin1Char('"') || c == QLatin1Char('\'')) {
  4129. close = c;
  4130. }
  4131. } else if (c == close) {
  4132. if (subst)
  4133. subst = false;
  4134. else
  4135. close = QChar();
  4136. }
  4137. }
  4138. cmd->cmd = line->mid(0, i).trimmed();
  4139. // command arguments starts with first non-letter character
  4140. cmd->args = cmd->cmd.section(QRegExp(_("(?=[^a-zA-Z])")), 1);
  4141. if (!cmd->args.isEmpty()) {
  4142. cmd->cmd.chop(cmd->args.size());
  4143. cmd->args = cmd->args.trimmed();
  4144. // '!' at the end of command
  4145. cmd->hasBang = cmd->args.startsWith(QLatin1Char('!'));
  4146. if (cmd->hasBang)
  4147. cmd->args = cmd->args.mid(1).trimmed();
  4148. }
  4149. // remove the first command from command line
  4150. line->remove(0, i + 1);
  4151. return true;
  4152. }
  4153. bool FakeVimHandler::Private::parseLineRange(QString *line, ExCommand *cmd)
  4154. {
  4155. // FIXME: that seems to be different for %w and %s
  4156. if (line->startsWith(QLatin1Char('%')))
  4157. line->replace(0, 1, _("1,$"));
  4158. int beginLine = parseLineAddress(line);
  4159. int endLine;
  4160. if (line->startsWith(QLatin1Char(','))) {
  4161. *line = line->mid(1).trimmed();
  4162. endLine = parseLineAddress(line);
  4163. } else {
  4164. endLine = beginLine;
  4165. }
  4166. if (beginLine == -1 || endLine == -1)
  4167. return false;
  4168. const int beginPos = firstPositionInLine(qMin(beginLine, endLine) + 1, false);
  4169. const int endPos = lastPositionInLine(qMax(beginLine, endLine) + 1, false);
  4170. cmd->range = Range(beginPos, endPos, RangeLineMode);
  4171. cmd->count = beginLine;
  4172. return true;
  4173. }
  4174. void FakeVimHandler::Private::parseRangeCount(const QString &line, Range *range) const
  4175. {
  4176. bool ok;
  4177. const int count = qAbs(line.trimmed().toInt(&ok));
  4178. if (ok) {
  4179. const int beginLine = document()->findBlock(range->endPos).blockNumber() + 1;
  4180. const int endLine = qMin(beginLine + count - 1, document()->blockCount());
  4181. range->beginPos = firstPositionInLine(beginLine, false);
  4182. range->endPos = lastPositionInLine(endLine, false);
  4183. }
  4184. }
  4185. // use handleExCommand for invoking commands that might move the cursor
  4186. void FakeVimHandler::Private::handleCommand(const QString &cmd)
  4187. {
  4188. handleExCommand(cmd);
  4189. }
  4190. bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
  4191. {
  4192. // :substitute
  4193. if (!cmd.matches(_("s"), _("substitute"))
  4194. && !(cmd.cmd.isEmpty() && !cmd.args.isEmpty() && QString::fromLatin1("&~").contains(cmd.args[0]))) {
  4195. return false;
  4196. }
  4197. int count = 1;
  4198. QString line = cmd.args;
  4199. const int countIndex = line.lastIndexOf(QRegExp(_("\\d+$")));
  4200. if (countIndex != -1) {
  4201. count = line.mid(countIndex).toInt();
  4202. line = line.mid(0, countIndex).trimmed();
  4203. }
  4204. if (cmd.cmd.isEmpty()) {
  4205. // keep previous substitution flags on '&&' and '~&'
  4206. if (line.size() > 1 && line[1] == QLatin1Char('&'))
  4207. g.lastSubstituteFlags += line.mid(2);
  4208. else
  4209. g.lastSubstituteFlags = line.mid(1);
  4210. if (line[0] == QLatin1Char('~'))
  4211. g.lastSubstitutePattern = g.lastSearch;
  4212. } else {
  4213. if (line.isEmpty()) {
  4214. g.lastSubstituteFlags.clear();
  4215. } else {
  4216. // we have /{pattern}/{string}/[flags] now
  4217. const QChar separator = line.at(0);
  4218. int pos1 = findUnescaped(separator, line, 1);
  4219. if (pos1 == -1)
  4220. return false;
  4221. int pos2 = findUnescaped(separator, line, pos1 + 1);;
  4222. if (pos2 == -1)
  4223. pos2 = line.size();
  4224. g.lastSubstitutePattern = line.mid(1, pos1 - 1);
  4225. g.lastSubstituteReplacement = line.mid(pos1 + 1, pos2 - pos1 - 1);
  4226. g.lastSubstituteFlags = line.mid(pos2 + 1);
  4227. }
  4228. }
  4229. count = qMax(1, count);
  4230. QString needle = g.lastSubstitutePattern;
  4231. if (g.lastSubstituteFlags.contains(QLatin1Char('i')))
  4232. needle.prepend(_("\\c"));
  4233. QRegExp pattern = vimPatternToQtPattern(needle, hasConfig(ConfigSmartCase));
  4234. QTextBlock lastBlock;
  4235. QTextBlock firstBlock;
  4236. const bool global = g.lastSubstituteFlags.contains(QLatin1Char('g'));
  4237. for (int a = 0; a != count; ++a) {
  4238. for (QTextBlock block = document()->findBlock(cmd.range.endPos);
  4239. block.isValid() && block.position() + block.length() > cmd.range.beginPos;
  4240. block = block.previous()) {
  4241. QString text = block.text();
  4242. if (substituteText(&text, pattern, g.lastSubstituteReplacement, global)) {
  4243. firstBlock = block;
  4244. if (!lastBlock.isValid()) {
  4245. lastBlock = block;
  4246. beginEditBlock();
  4247. }
  4248. QTextCursor tc = cursor();
  4249. const int pos = block.position();
  4250. const int anchor = pos + block.length() - 1;
  4251. tc.setPosition(anchor);
  4252. tc.setPosition(pos, KeepAnchor);
  4253. tc.insertText(text);
  4254. }
  4255. }
  4256. }
  4257. if (lastBlock.isValid()) {
  4258. State &state = m_undo.top();
  4259. state.position = CursorPosition(firstBlock.blockNumber(), 0);
  4260. leaveVisualMode();
  4261. setPosition(lastBlock.position());
  4262. setAnchor();
  4263. moveToFirstNonBlankOnLine();
  4264. setTargetColumn();
  4265. endEditBlock();
  4266. }
  4267. return true;
  4268. }
  4269. bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map
  4270. {
  4271. QByteArray modes;
  4272. enum Type { Map, Noremap, Unmap } type;
  4273. QByteArray cmd = cmd0.cmd.toLatin1();
  4274. // Strange formatting. But everything else is even uglier.
  4275. if (cmd == "map") { modes = "nvo"; type = Map; } else
  4276. if (cmd == "nm" || cmd == "nmap") { modes = "n"; type = Map; } else
  4277. if (cmd == "vm" || cmd == "vmap") { modes = "v"; type = Map; } else
  4278. if (cmd == "xm" || cmd == "xmap") { modes = "x"; type = Map; } else
  4279. if (cmd == "smap") { modes = "s"; type = Map; } else
  4280. if (cmd == "map!") { modes = "ic"; type = Map; } else
  4281. if (cmd == "im" || cmd == "imap") { modes = "i"; type = Map; } else
  4282. if (cmd == "lm" || cmd == "lmap") { modes = "l"; type = Map; } else
  4283. if (cmd == "cm" || cmd == "cmap") { modes = "c"; type = Map; } else
  4284. if (cmd == "no" || cmd == "noremap") { modes = "nvo"; type = Noremap; } else
  4285. if (cmd == "nn" || cmd == "nnoremap") { modes = "n"; type = Noremap; } else
  4286. if (cmd == "vn" || cmd == "vnoremap") { modes = "v"; type = Noremap; } else
  4287. if (cmd == "xn" || cmd == "xnoremap") { modes = "x"; type = Noremap; } else
  4288. if (cmd == "snor" || cmd == "snoremap") { modes = "s"; type = Noremap; } else
  4289. if (cmd == "ono" || cmd == "onoremap") { modes = "o"; type = Noremap; } else
  4290. if (cmd == "no!" || cmd == "noremap!") { modes = "ic"; type = Noremap; } else
  4291. if (cmd == "ino" || cmd == "inoremap") { modes = "i"; type = Noremap; } else
  4292. if (cmd == "ln" || cmd == "lnoremap") { modes = "l"; type = Noremap; } else
  4293. if (cmd == "cno" || cmd == "cnoremap") { modes = "c"; type = Noremap; } else
  4294. if (cmd == "unm" || cmd == "unmap") { modes = "nvo"; type = Unmap; } else
  4295. if (cmd == "nun" || cmd == "nunmap") { modes = "n"; type = Unmap; } else
  4296. if (cmd == "vu" || cmd == "vunmap") { modes = "v"; type = Unmap; } else
  4297. if (cmd == "xu" || cmd == "xunmap") { modes = "x"; type = Unmap; } else
  4298. if (cmd == "sunm" || cmd == "sunmap") { modes = "s"; type = Unmap; } else
  4299. if (cmd == "ou" || cmd == "ounmap") { modes = "o"; type = Unmap; } else
  4300. if (cmd == "unm!" || cmd == "unmap!") { modes = "ic"; type = Unmap; } else
  4301. if (cmd == "iu" || cmd == "iunmap") { modes = "i"; type = Unmap; } else
  4302. if (cmd == "lu" || cmd == "lunmap") { modes = "l"; type = Unmap; } else
  4303. if (cmd == "cu" || cmd == "cunmap") { modes = "c"; type = Unmap; }
  4304. else
  4305. return false;
  4306. QString args = cmd0.args;
  4307. bool silent = false;
  4308. bool unique = false;
  4309. forever {
  4310. if (eatString("<silent>", &args)) {
  4311. silent = true;
  4312. } else if (eatString("<unique>", &args)) {
  4313. continue;
  4314. } else if (eatString("<special>", &args)) {
  4315. continue;
  4316. } else if (eatString("<buffer>", &args)) {
  4317. notImplementedYet();
  4318. continue;
  4319. } else if (eatString("<script>", &args)) {
  4320. notImplementedYet();
  4321. continue;
  4322. } else if (eatString("<expr>", &args)) {
  4323. notImplementedYet();
  4324. return true;
  4325. }
  4326. break;
  4327. }
  4328. const QString lhs = args.section(QRegExp(_("\\s+")), 0, 0);
  4329. const QString rhs = args.section(QRegExp(_("\\s+")), 1);
  4330. if ((rhs.isNull() && type != Unmap) || (!rhs.isNull() && type == Unmap)) {
  4331. // FIXME: Dump mappings here.
  4332. //qDebug() << g.mappings;
  4333. return true;
  4334. }
  4335. Inputs key(lhs);
  4336. //qDebug() << "MAPPING: " << modes << lhs << rhs;
  4337. switch (type) {
  4338. case Unmap:
  4339. foreach (char c, modes)
  4340. MappingsIterator(&g.mappings, c, key).remove();
  4341. break;
  4342. case Map: // fall through
  4343. case Noremap: {
  4344. Inputs inputs(rhs, type == Noremap, silent);
  4345. foreach (char c, modes)
  4346. MappingsIterator(&g.mappings, c).setInputs(key, inputs, unique);
  4347. break;
  4348. }
  4349. }
  4350. return true;
  4351. }
  4352. bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd)
  4353. {
  4354. // :his[tory]
  4355. if (!cmd.matches(_("his"), _("history")))
  4356. return false;
  4357. if (cmd.args.isEmpty()) {
  4358. QString info;
  4359. info += _("# command history\n");
  4360. int i = 0;
  4361. foreach (const QString &item, g.commandBuffer.historyItems()) {
  4362. ++i;
  4363. info += QString::fromLatin1("%1 %2\n").arg(i, -8).arg(item);
  4364. }
  4365. emit q->extraInformationChanged(info);
  4366. } else {
  4367. notImplementedYet();
  4368. }
  4369. updateMiniBuffer();
  4370. return true;
  4371. }
  4372. bool FakeVimHandler::Private::handleExRegisterCommand(const ExCommand &cmd)
  4373. {
  4374. // :reg[isters] and :di[splay]
  4375. if (!cmd.matches(_("reg"), _("registers")) && !cmd.matches(_("di"), _("display")))
  4376. return false;
  4377. QByteArray regs = cmd.args.toLatin1();
  4378. if (regs.isEmpty()) {
  4379. regs = "\"0123456789";
  4380. QHashIterator<int, Register> it(g.registers);
  4381. while (it.hasNext()) {
  4382. it.next();
  4383. if (it.key() > '9')
  4384. regs += char(it.key());
  4385. }
  4386. }
  4387. QString info;
  4388. info += _("--- Registers ---\n");
  4389. foreach (char reg, regs) {
  4390. QString value = quoteUnprintable(registerContents(reg));
  4391. info += QString::fromLatin1("\"%1 %2\n").arg(reg).arg(value);
  4392. }
  4393. emit q->extraInformationChanged(info);
  4394. updateMiniBuffer();
  4395. return true;
  4396. }
  4397. bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd)
  4398. {
  4399. // :se[t]
  4400. if (!cmd.matches(_("se"), _("set")))
  4401. return false;
  4402. clearMessage();
  4403. SavedAction *act = theFakeVimSettings()->item(cmd.args);
  4404. QTC_CHECK(!cmd.args.isEmpty()); // Handled by plugin.
  4405. if (act && act->value().canConvert(QVariant::Bool)) {
  4406. // Boolean config to be switched on.
  4407. bool oldValue = act->value().toBool();
  4408. if (oldValue == false)
  4409. act->setValue(true);
  4410. else if (oldValue == true)
  4411. {} // nothing to do
  4412. } else if (act) {
  4413. // Non-boolean to show.
  4414. showMessage(MessageInfo, cmd.args + QLatin1Char('=') + act->value().toString());
  4415. } else if (cmd.args.startsWith(_("no"))
  4416. && (act = theFakeVimSettings()->item(cmd.args.mid(2)))) {
  4417. // Boolean config to be switched off.
  4418. bool oldValue = act->value().toBool();
  4419. if (oldValue == true)
  4420. act->setValue(false);
  4421. else if (oldValue == false)
  4422. {} // nothing to do
  4423. } else if (cmd.args.contains(QLatin1Char('='))) {
  4424. // Non-boolean config to set.
  4425. int p = cmd.args.indexOf(QLatin1Char('='));
  4426. QString error = theFakeVimSettings()
  4427. ->trySetValue(cmd.args.left(p), cmd.args.mid(p + 1));
  4428. if (!error.isEmpty())
  4429. showMessage(MessageError, error);
  4430. } else {
  4431. showMessage(MessageError, FakeVimHandler::tr("Unknown option: ") + cmd.args);
  4432. }
  4433. updateMiniBuffer();
  4434. updateEditor();
  4435. return true;
  4436. }
  4437. bool FakeVimHandler::Private::handleExNormalCommand(const ExCommand &cmd)
  4438. {
  4439. // :norm[al]
  4440. if (!cmd.matches(_("norm"), _("normal")))
  4441. return false;
  4442. //qDebug() << "REPLAY NORMAL: " << quoteUnprintable(reNormal.cap(3));
  4443. replay(cmd.args);
  4444. return true;
  4445. }
  4446. bool FakeVimHandler::Private::handleExYankDeleteCommand(const ExCommand &cmd)
  4447. {
  4448. // :[range]d[elete] [x] [count]
  4449. // :[range]y[ank] [x] [count]
  4450. const bool remove = cmd.matches(_("d"), _("delete"));
  4451. if (!remove && !cmd.matches(_("y"), _("yank")))
  4452. return false;
  4453. // get register from arguments
  4454. const bool hasRegisterArg = !cmd.args.isEmpty() && !cmd.args.at(0).isDigit();
  4455. const int r = hasRegisterArg ? cmd.args.at(0).unicode() : m_register;
  4456. // get [count] from arguments
  4457. Range range = cmd.range;
  4458. parseRangeCount(cmd.args.mid(hasRegisterArg ? 1 : 0).trimmed(), &range);
  4459. yankText(range, r);
  4460. if (remove) {
  4461. leaveVisualMode();
  4462. setPosition(range.beginPos);
  4463. setUndoPosition();
  4464. setCurrentRange(range);
  4465. removeText(currentRange());
  4466. }
  4467. return true;
  4468. }
  4469. bool FakeVimHandler::Private::handleExChangeCommand(const ExCommand &cmd)
  4470. {
  4471. // :[range]c[hange]
  4472. if (!cmd.matches(_("c"), _("change")))
  4473. return false;
  4474. const bool oldAutoIndent = hasConfig(ConfigAutoIndent);
  4475. // Temporarily set autoindent if ! is present.
  4476. if (cmd.hasBang)
  4477. theFakeVimSetting(ConfigAutoIndent)->setValue(true, false);
  4478. Range range = cmd.range;
  4479. range.rangemode = RangeLineModeExclusive;
  4480. removeText(range);
  4481. insertAutomaticIndentation(true);
  4482. // FIXME: In Vim same or less number of lines can be inserted and position after insertion is
  4483. // beginning of last inserted line.
  4484. enterInsertMode();
  4485. if (cmd.hasBang && !oldAutoIndent)
  4486. theFakeVimSetting(ConfigAutoIndent)->setValue(false, false);
  4487. return true;
  4488. }
  4489. bool FakeVimHandler::Private::handleExMoveCommand(const ExCommand &cmd)
  4490. {
  4491. // :[range]m[ove] {address}
  4492. if (!cmd.matches(_("m"), _("move")))
  4493. return false;
  4494. QString lineCode = cmd.args;
  4495. const int startLine = document()->findBlock(cmd.range.beginPos).blockNumber();
  4496. const int endLine = document()->findBlock(cmd.range.endPos).blockNumber();
  4497. const int lines = endLine - startLine + 1;
  4498. int targetLine = lineCode == _("0") ? -1 : parseLineAddress(&lineCode);
  4499. if (targetLine >= startLine && targetLine < endLine) {
  4500. showMessage(MessageError, FakeVimHandler::tr("Move lines into themselves"));
  4501. return true;
  4502. }
  4503. CursorPosition lastAnchor = mark(QLatin1Char('<')).position;
  4504. CursorPosition lastPosition = mark(QLatin1Char('>')).position;
  4505. recordJump();
  4506. setPosition(cmd.range.beginPos);
  4507. setUndoPosition();
  4508. setCurrentRange(cmd.range);
  4509. QString text = selectText(cmd.range);
  4510. removeText(currentRange());
  4511. const bool insertAtEnd = targetLine == document()->blockCount();
  4512. if (targetLine >= startLine)
  4513. targetLine -= lines;
  4514. QTextBlock block = document()->findBlockByNumber(insertAtEnd ? targetLine : targetLine + 1);
  4515. setPosition(block.position());
  4516. setAnchor();
  4517. if (insertAtEnd) {
  4518. moveBehindEndOfLine();
  4519. text.chop(1);
  4520. insertText(QString::fromLatin1("\n"));
  4521. }
  4522. insertText(text);
  4523. if (!insertAtEnd)
  4524. moveUp(1);
  4525. if (hasConfig(ConfigStartOfLine))
  4526. moveToFirstNonBlankOnLine();
  4527. // correct last selection
  4528. leaveVisualMode();
  4529. if (lastAnchor.line >= startLine && lastAnchor.line <= endLine)
  4530. lastAnchor.line += targetLine - startLine + 1;
  4531. if (lastPosition.line >= startLine && lastPosition.line <= endLine)
  4532. lastPosition.line += targetLine - startLine + 1;
  4533. setMark(QLatin1Char('<'), lastAnchor);
  4534. setMark(QLatin1Char('>'), lastPosition);
  4535. if (lines > 2)
  4536. showMessage(MessageInfo, FakeVimHandler::tr("%1 lines moved").arg(lines));
  4537. return true;
  4538. }
  4539. bool FakeVimHandler::Private::handleExJoinCommand(const ExCommand &cmd)
  4540. {
  4541. // :[range]j[oin][!] [count]
  4542. // FIXME: Argument [count] can follow immediately.
  4543. if (!cmd.matches(_("j"), _("join")))
  4544. return false;
  4545. // get [count] from arguments
  4546. bool ok;
  4547. int count = cmd.args.toInt(&ok);
  4548. if (ok) {
  4549. setPosition(cmd.range.endPos);
  4550. } else {
  4551. setPosition(cmd.range.beginPos);
  4552. const int startLine = document()->findBlock(cmd.range.beginPos).blockNumber();
  4553. const int endLine = document()->findBlock(cmd.range.endPos).blockNumber();
  4554. count = endLine - startLine + 1;
  4555. }
  4556. moveToStartOfLine();
  4557. setUndoPosition();
  4558. joinLines(count, cmd.hasBang);
  4559. moveToFirstNonBlankOnLine();
  4560. return true;
  4561. }
  4562. bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd)
  4563. {
  4564. // :w, :x, :wq, ...
  4565. //static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
  4566. if (cmd.cmd != _("w") && cmd.cmd != _("x") && cmd.cmd != _("wq"))
  4567. return false;
  4568. int beginLine = lineForPosition(cmd.range.beginPos);
  4569. int endLine = lineForPosition(cmd.range.endPos);
  4570. const bool noArgs = (beginLine == -1);
  4571. if (beginLine == -1)
  4572. beginLine = 0;
  4573. if (endLine == -1)
  4574. endLine = linesInDocument();
  4575. //qDebug() << "LINES: " << beginLine << endLine;
  4576. //QString prefix = cmd.args;
  4577. const bool forced = cmd.hasBang;
  4578. //const bool quit = prefix.contains(QLatin1Char('q')) || prefix.contains(QLatin1Char('x'));
  4579. //const bool quitAll = quit && prefix.contains(QLatin1Char('a'));
  4580. QString fileName = cmd.args;
  4581. if (fileName.isEmpty())
  4582. fileName = m_currentFileName;
  4583. QFile file1(fileName);
  4584. const bool exists = file1.exists();
  4585. if (exists && !forced && !noArgs) {
  4586. showMessage(MessageError, FakeVimHandler::tr
  4587. ("File \"%1\" exists (add ! to override)").arg(fileName));
  4588. } else if (file1.open(QIODevice::ReadWrite)) {
  4589. // Nobody cared, so act ourselves.
  4590. file1.close();
  4591. Range range(firstPositionInLine(beginLine),
  4592. firstPositionInLine(endLine), RangeLineMode);
  4593. QString contents = selectText(range);
  4594. QFile::remove(fileName);
  4595. QFile file2(fileName);
  4596. if (file2.open(QIODevice::ReadWrite)) {
  4597. QTextStream ts(&file2);
  4598. ts << contents;
  4599. } else {
  4600. showMessage(MessageError, FakeVimHandler::tr
  4601. ("Cannot open file \"%1\" for writing").arg(fileName));
  4602. }
  4603. // Check result by reading back.
  4604. QFile file3(fileName);
  4605. file3.open(QIODevice::ReadOnly);
  4606. QByteArray ba = file3.readAll();
  4607. showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2 %3L, %4C written")
  4608. .arg(fileName).arg(exists ? _(" ") : tr(" [New] "))
  4609. .arg(ba.count('\n')).arg(ba.size()));
  4610. //if (quitAll)
  4611. // passUnknownExCommand(forced ? "qa!" : "qa");
  4612. //else if (quit)
  4613. // passUnknownExCommand(forced ? "q!" : "q");
  4614. } else {
  4615. showMessage(MessageError, FakeVimHandler::tr
  4616. ("Cannot open file \"%1\" for reading").arg(fileName));
  4617. }
  4618. return true;
  4619. }
  4620. bool FakeVimHandler::Private::handleExReadCommand(const ExCommand &cmd)
  4621. {
  4622. // :r[ead]
  4623. if (!cmd.matches(_("r"), _("read")))
  4624. return false;
  4625. beginEditBlock();
  4626. moveToStartOfLine();
  4627. setTargetColumn();
  4628. moveDown();
  4629. m_currentFileName = cmd.args;
  4630. QFile file(m_currentFileName);
  4631. file.open(QIODevice::ReadOnly);
  4632. QTextStream ts(&file);
  4633. QString data = ts.readAll();
  4634. insertText(data);
  4635. endEditBlock();
  4636. showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2L, %3C")
  4637. .arg(m_currentFileName).arg(data.count(QLatin1Char('\n'))).arg(data.size()));
  4638. return true;
  4639. }
  4640. bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :!
  4641. {
  4642. if (!cmd.cmd.isEmpty() || !cmd.hasBang)
  4643. return false;
  4644. setCurrentRange(cmd.range);
  4645. int targetPosition = firstPositionInLine(lineForPosition(cmd.range.beginPos));
  4646. QString command = QString(cmd.cmd.mid(1) + QLatin1Char(' ') + cmd.args).trimmed();
  4647. QString text = selectText(cmd.range);
  4648. QProcess proc;
  4649. proc.start(command);
  4650. proc.waitForStarted();
  4651. if (Utils::HostOsInfo::isWindowsHost())
  4652. text.replace(_("\n"), _("\r\n"));
  4653. proc.write(text.toUtf8());
  4654. proc.closeWriteChannel();
  4655. proc.waitForFinished();
  4656. QString result = QString::fromUtf8(proc.readAllStandardOutput());
  4657. if (text.isEmpty()) {
  4658. emit q->extraInformationChanged(result);
  4659. } else {
  4660. beginEditBlock();
  4661. removeText(currentRange());
  4662. insertText(result);
  4663. setPosition(targetPosition);
  4664. endEditBlock();
  4665. leaveVisualMode();
  4666. //qDebug() << "FILTER: " << command;
  4667. showMessage(MessageInfo, FakeVimHandler::tr("%n lines filtered", 0,
  4668. text.count(QLatin1Char('\n'))));
  4669. }
  4670. return true;
  4671. }
  4672. bool FakeVimHandler::Private::handleExShiftCommand(const ExCommand &cmd)
  4673. {
  4674. // :[range]{<|>}* [count]
  4675. if (!cmd.cmd.isEmpty() || (!cmd.args.startsWith(QLatin1Char('<')) && !cmd.args.startsWith(QLatin1Char('>'))))
  4676. return false;
  4677. const QChar c = cmd.args.at(0);
  4678. // get number of repetition
  4679. int repeat = 1;
  4680. int i = 1;
  4681. for (; i < cmd.args.size(); ++i) {
  4682. const QChar c2 = cmd.args.at(i);
  4683. if (c2 == c)
  4684. ++repeat;
  4685. else if (!c2.isSpace())
  4686. break;
  4687. }
  4688. // get [count] from arguments
  4689. Range range = cmd.range;
  4690. parseRangeCount(cmd.args.mid(i), &range);
  4691. setCurrentRange(range);
  4692. if (c == QLatin1Char('<'))
  4693. shiftRegionLeft(repeat);
  4694. else
  4695. shiftRegionRight(repeat);
  4696. leaveVisualMode();
  4697. return true;
  4698. }
  4699. bool FakeVimHandler::Private::handleExNohlsearchCommand(const ExCommand &cmd)
  4700. {
  4701. // :nohlsearch
  4702. if (!cmd.cmd.startsWith(_("noh")))
  4703. return false;
  4704. highlightMatches(QString());
  4705. return true;
  4706. }
  4707. bool FakeVimHandler::Private::handleExUndoRedoCommand(const ExCommand &cmd)
  4708. {
  4709. // :undo
  4710. // :redo
  4711. bool undo = (cmd.cmd == _("u") || cmd.cmd == _("un") || cmd.cmd == _("undo"));
  4712. if (!undo && cmd.cmd != _("red") && cmd.cmd != _("redo"))
  4713. return false;
  4714. undoRedo(undo);
  4715. updateMiniBuffer();
  4716. return true;
  4717. }
  4718. bool FakeVimHandler::Private::handleExGotoCommand(const ExCommand &cmd)
  4719. {
  4720. // :{address}
  4721. if (!cmd.cmd.isEmpty() || !cmd.args.isEmpty())
  4722. return false;
  4723. const int beginLine = lineForPosition(cmd.range.endPos);
  4724. setPosition(firstPositionInLine(beginLine));
  4725. clearMessage();
  4726. return true;
  4727. }
  4728. bool FakeVimHandler::Private::handleExSourceCommand(const ExCommand &cmd)
  4729. {
  4730. // :source
  4731. if (cmd.cmd != _("so") && cmd.cmd != _("source"))
  4732. return false;
  4733. QString fileName = cmd.args;
  4734. QFile file(fileName);
  4735. if (!file.open(QIODevice::ReadOnly)) {
  4736. showMessage(MessageError, FakeVimHandler::tr("Cannot open file %1").arg(fileName));
  4737. return true;
  4738. }
  4739. bool inFunction = false;
  4740. QByteArray line;
  4741. while (!file.atEnd() || !line.isEmpty()) {
  4742. QByteArray nextline = !file.atEnd() ? file.readLine() : QByteArray();
  4743. // remove comment
  4744. int i = nextline.lastIndexOf('"');
  4745. if (i != -1)
  4746. nextline = nextline.remove(i, nextline.size() - i);
  4747. nextline = nextline.trimmed();
  4748. // multi-line command?
  4749. if (nextline.startsWith('\\')) {
  4750. line += nextline.mid(1);
  4751. continue;
  4752. }
  4753. if (line.startsWith("function")) {
  4754. //qDebug() << "IGNORING FUNCTION" << line;
  4755. inFunction = true;
  4756. } else if (inFunction && line.startsWith("endfunction")) {
  4757. inFunction = false;
  4758. } else if (!line.isEmpty() && !inFunction) {
  4759. //qDebug() << "EXECUTING: " << line;
  4760. ExCommand cmd;
  4761. QString commandLine = QString::fromLocal8Bit(line);
  4762. while (parseExCommmand(&commandLine, &cmd)) {
  4763. if (!handleExCommandHelper(cmd))
  4764. break;
  4765. }
  4766. }
  4767. line = nextline;
  4768. }
  4769. file.close();
  4770. return true;
  4771. }
  4772. bool FakeVimHandler::Private::handleExEchoCommand(const ExCommand &cmd)
  4773. {
  4774. // :echo
  4775. if (cmd.cmd != _("echo"))
  4776. return false;
  4777. showMessage(MessageInfo, cmd.args);
  4778. return true;
  4779. }
  4780. void FakeVimHandler::Private::handleExCommand(const QString &line0)
  4781. {
  4782. QString line = line0; // Make sure we have a copy to prevent aliasing.
  4783. if (line.endsWith(QLatin1Char('%'))) {
  4784. line.chop(1);
  4785. int percent = line.toInt();
  4786. setPosition(firstPositionInLine(percent * linesInDocument() / 100));
  4787. clearMessage();
  4788. return;
  4789. }
  4790. //qDebug() << "CMD: " << cmd;
  4791. enterCommandMode(g.returnToMode);
  4792. beginLargeEditBlock();
  4793. ExCommand cmd;
  4794. QString lastCommand = line;
  4795. while (parseExCommmand(&line, &cmd)) {
  4796. if (!handleExCommandHelper(cmd)) {
  4797. showMessage(MessageError, tr("Not an editor command: %1").arg(lastCommand));
  4798. break;
  4799. }
  4800. lastCommand = line;
  4801. }
  4802. endEditBlock();
  4803. resetCommandMode();
  4804. }
  4805. bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
  4806. {
  4807. return handleExPluginCommand(cmd)
  4808. || handleExGotoCommand(cmd)
  4809. || handleExBangCommand(cmd)
  4810. || handleExHistoryCommand(cmd)
  4811. || handleExRegisterCommand(cmd)
  4812. || handleExYankDeleteCommand(cmd)
  4813. || handleExChangeCommand(cmd)
  4814. || handleExMoveCommand(cmd)
  4815. || handleExJoinCommand(cmd)
  4816. || handleExMapCommand(cmd)
  4817. || handleExNohlsearchCommand(cmd)
  4818. || handleExNormalCommand(cmd)
  4819. || handleExReadCommand(cmd)
  4820. || handleExUndoRedoCommand(cmd)
  4821. || handleExSetCommand(cmd)
  4822. || handleExShiftCommand(cmd)
  4823. || handleExSourceCommand(cmd)
  4824. || handleExSubstituteCommand(cmd)
  4825. || handleExWriteCommand(cmd)
  4826. || handleExEchoCommand(cmd);
  4827. }
  4828. bool FakeVimHandler::Private::handleExPluginCommand(const ExCommand &cmd)
  4829. {
  4830. bool handled = false;
  4831. emit q->handleExCommandRequested(&handled, cmd);
  4832. //qDebug() << "HANDLER REQUEST: " << cmd.cmd << handled;
  4833. return handled;
  4834. }
  4835. void FakeVimHandler::Private::searchBalanced(bool forward, QChar needle, QChar other)
  4836. {
  4837. int level = 1;
  4838. int pos = position();
  4839. const int npos = forward ? lastPositionInDocument() : 0;
  4840. while (true) {
  4841. if (forward)
  4842. ++pos;
  4843. else
  4844. --pos;
  4845. if (pos == npos)
  4846. return;
  4847. QChar c = document()->characterAt(pos);
  4848. if (c == other)
  4849. ++level;
  4850. else if (c == needle)
  4851. --level;
  4852. if (level == 0) {
  4853. const int oldLine = cursorLine() - cursorLineOnScreen();
  4854. // Making this unconditional feels better, but is not "vim like".
  4855. if (oldLine != cursorLine() - cursorLineOnScreen())
  4856. scrollToLine(cursorLine() - linesOnScreen() / 2);
  4857. recordJump();
  4858. setPosition(pos);
  4859. setTargetColumn();
  4860. return;
  4861. }
  4862. }
  4863. }
  4864. QTextCursor FakeVimHandler::Private::search(const SearchData &sd, int startPos, int count,
  4865. bool showMessages)
  4866. {
  4867. QRegExp needleExp = vimPatternToQtPattern(sd.needle, hasConfig(ConfigSmartCase));
  4868. if (!needleExp.isValid()) {
  4869. if (showMessages) {
  4870. QString error = needleExp.errorString();
  4871. showMessage(MessageError,
  4872. FakeVimHandler::tr("Invalid regular expression: %1").arg(error));
  4873. }
  4874. if (sd.highlightMatches)
  4875. highlightMatches(QString());
  4876. return QTextCursor();
  4877. }
  4878. int repeat = count;
  4879. const int pos = startPos + (sd.forward ? 1 : -1);
  4880. QTextCursor tc;
  4881. if (pos >= 0 && pos < document()->characterCount()) {
  4882. tc = QTextCursor(document());
  4883. tc.setPosition(pos);
  4884. if (sd.forward && afterEndOfLine(document(), pos))
  4885. tc.movePosition(Right);
  4886. if (!tc.isNull()) {
  4887. if (sd.forward)
  4888. searchForward(&tc, needleExp, &repeat);
  4889. else
  4890. searchBackward(&tc, needleExp, &repeat);
  4891. }
  4892. }
  4893. if (tc.isNull()) {
  4894. if (hasConfig(ConfigWrapScan)) {
  4895. tc = QTextCursor(document());
  4896. tc.movePosition(sd.forward ? StartOfDocument : EndOfDocument);
  4897. if (sd.forward)
  4898. searchForward(&tc, needleExp, &repeat);
  4899. else
  4900. searchBackward(&tc, needleExp, &repeat);
  4901. if (tc.isNull()) {
  4902. if (showMessages) {
  4903. showMessage(MessageError,
  4904. FakeVimHandler::tr("Pattern not found: %1").arg(sd.needle));
  4905. }
  4906. } else if (showMessages) {
  4907. QString msg = sd.forward
  4908. ? FakeVimHandler::tr("search hit BOTTOM, continuing at TOP")
  4909. : FakeVimHandler::tr("search hit TOP, continuing at BOTTOM");
  4910. showMessage(MessageWarning, msg);
  4911. }
  4912. } else if (showMessages) {
  4913. QString msg = sd.forward
  4914. ? FakeVimHandler::tr("search hit BOTTOM without match for: %1")
  4915. : FakeVimHandler::tr("search hit TOP without match for: %1");
  4916. showMessage(MessageError, msg.arg(sd.needle));
  4917. }
  4918. }
  4919. if (sd.highlightMatches)
  4920. highlightMatches(needleExp.pattern());
  4921. return tc;
  4922. }
  4923. void FakeVimHandler::Private::search(const SearchData &sd, bool showMessages)
  4924. {
  4925. const int oldLine = cursorLine() - cursorLineOnScreen();
  4926. QTextCursor tc = search(sd, m_searchStartPosition, count(), showMessages);
  4927. if (tc.isNull()) {
  4928. tc = cursor();
  4929. tc.setPosition(m_searchStartPosition);
  4930. }
  4931. if (isVisualMode()) {
  4932. int d = tc.anchor() - tc.position();
  4933. setPosition(tc.position() + d);
  4934. } else {
  4935. // Set Cursor. In contrast to the main editor we have the cursor
  4936. // position before the anchor position.
  4937. setAnchorAndPosition(tc.position(), tc.anchor());
  4938. }
  4939. // Making this unconditional feels better, but is not "vim like".
  4940. if (oldLine != cursorLine() - cursorLineOnScreen())
  4941. scrollToLine(cursorLine() - linesOnScreen() / 2);
  4942. m_searchCursor = cursor();
  4943. setTargetColumn();
  4944. }
  4945. void FakeVimHandler::Private::searchNext(bool forward)
  4946. {
  4947. SearchData sd;
  4948. sd.needle = g.lastSearch;
  4949. sd.forward = forward ? g.lastSearchForward : !g.lastSearchForward;
  4950. sd.highlightMatches = true;
  4951. m_searchStartPosition = position();
  4952. showMessage(MessageCommand, QLatin1Char(g.lastSearchForward ? '/' : '?') + sd.needle);
  4953. recordJump();
  4954. search(sd);
  4955. }
  4956. void FakeVimHandler::Private::highlightMatches(const QString &needle)
  4957. {
  4958. if (!hasConfig(ConfigHlSearch) || needle == m_oldNeedle)
  4959. return;
  4960. m_oldNeedle = needle;
  4961. updateHighlights();
  4962. }
  4963. void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
  4964. {
  4965. QTextCursor tc2 = cursor();
  4966. moveToFirstNonBlankOnLine(&tc2);
  4967. setPosition(tc2.position());
  4968. }
  4969. void FakeVimHandler::Private::moveToFirstNonBlankOnLine(QTextCursor *tc)
  4970. {
  4971. QTextDocument *doc = tc->document();
  4972. int firstPos = tc->block().position();
  4973. for (int i = firstPos, n = firstPos + block().length(); i < n; ++i) {
  4974. if (!doc->characterAt(i).isSpace() || i == n - 1) {
  4975. tc->setPosition(i);
  4976. return;
  4977. }
  4978. }
  4979. tc->setPosition(block().position());
  4980. }
  4981. void FakeVimHandler::Private::indentSelectedText(QChar typedChar)
  4982. {
  4983. beginEditBlock();
  4984. setTargetColumn();
  4985. int beginLine = qMin(lineForPosition(position()), lineForPosition(anchor()));
  4986. int endLine = qMax(lineForPosition(position()), lineForPosition(anchor()));
  4987. Range range(anchor(), position(), m_rangemode);
  4988. indentText(range, typedChar);
  4989. setPosition(firstPositionInLine(beginLine));
  4990. handleStartOfLine();
  4991. setTargetColumn();
  4992. setDotCommand(_("%1=="), endLine - beginLine + 1);
  4993. endEditBlock();
  4994. const int lines = endLine - beginLine + 1;
  4995. if (lines > 2)
  4996. showMessage(MessageInfo, FakeVimHandler::tr("%n lines indented", 0, lines));
  4997. }
  4998. void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar)
  4999. {
  5000. int beginBlock = document()->findBlock(range.beginPos).blockNumber();
  5001. int endBlock = document()->findBlock(range.endPos).blockNumber();
  5002. if (beginBlock > endBlock)
  5003. qSwap(beginBlock, endBlock);
  5004. emit q->indentRegion(beginBlock, endBlock, typedChar);
  5005. }
  5006. bool FakeVimHandler::Private::isElectricCharacter(QChar c) const
  5007. {
  5008. bool result = false;
  5009. emit q->checkForElectricCharacter(&result, c);
  5010. return result;
  5011. }
  5012. void FakeVimHandler::Private::shiftRegionRight(int repeat)
  5013. {
  5014. int beginLine = lineForPosition(anchor());
  5015. int endLine = lineForPosition(position());
  5016. int targetPos = anchor();
  5017. if (beginLine > endLine) {
  5018. qSwap(beginLine, endLine);
  5019. targetPos = position();
  5020. }
  5021. if (hasConfig(ConfigStartOfLine))
  5022. targetPos = firstPositionInLine(beginLine);
  5023. const int sw = config(ConfigShiftWidth).toInt();
  5024. m_movetype = MoveLineWise;
  5025. beginEditBlock();
  5026. QTextBlock block = document()->findBlockByLineNumber(beginLine - 1);
  5027. while (block.isValid() && lineNumber(block) <= endLine) {
  5028. const Column col = indentation(block.text());
  5029. QTextCursor tc = cursor();
  5030. tc.setPosition(block.position());
  5031. if (col.physical > 0)
  5032. tc.setPosition(tc.position() + col.physical, KeepAnchor);
  5033. tc.insertText(tabExpand(col.logical + sw * repeat));
  5034. block = block.next();
  5035. }
  5036. endEditBlock();
  5037. setPosition(targetPos);
  5038. handleStartOfLine();
  5039. setTargetColumn();
  5040. const int lines = endLine - beginLine + 1;
  5041. if (lines > 2) {
  5042. showMessage(MessageInfo,
  5043. FakeVimHandler::tr("%n lines %1ed %2 time", 0, lines)
  5044. .arg(repeat > 0 ? '>' : '<').arg(qAbs(repeat)));
  5045. }
  5046. }
  5047. void FakeVimHandler::Private::shiftRegionLeft(int repeat)
  5048. {
  5049. shiftRegionRight(-repeat);
  5050. }
  5051. void FakeVimHandler::Private::moveToTargetColumn()
  5052. {
  5053. const QTextBlock &bl = block();
  5054. //Column column = cursorColumn();
  5055. //int logical = logical
  5056. const int pos = lastPositionInLine(bl.blockNumber() + 1, false);
  5057. if (m_targetColumn == -1) {
  5058. setPosition(pos);
  5059. return;
  5060. }
  5061. const int physical = bl.position() + logicalToPhysicalColumn(m_targetColumn, bl.text());
  5062. //qDebug() << "CORRECTING COLUMN FROM: " << logical << "TO" << m_targetColumn;
  5063. setPosition(qMin(pos, physical));
  5064. }
  5065. /* if simple is given:
  5066. * class 0: spaces
  5067. * class 1: non-spaces
  5068. * else
  5069. * class 0: spaces
  5070. * class 1: non-space-or-letter-or-number
  5071. * class 2: letter-or-number
  5072. */
  5073. int FakeVimHandler::Private::charClass(QChar c, bool simple) const
  5074. {
  5075. if (simple)
  5076. return c.isSpace() ? 0 : 1;
  5077. // FIXME: This means that only characters < 256 in the
  5078. // ConfigIsKeyword setting are handled properly.
  5079. if (c.unicode() < 256) {
  5080. //int old = (c.isLetterOrNumber() || c.unicode() == QLatin1Char('_')) ? 2
  5081. // : c.isSpace() ? 0 : 1;
  5082. //qDebug() << c.unicode() << old << m_charClass[c.unicode()];
  5083. return m_charClass[c.unicode()];
  5084. }
  5085. if (c.isLetterOrNumber() || c.unicode() == QLatin1Char('_'))
  5086. return 2;
  5087. return c.isSpace() ? 0 : 1;
  5088. }
  5089. void FakeVimHandler::Private::miniBufferTextEdited(const QString &text, int cursorPos,
  5090. int anchorPos)
  5091. {
  5092. if (m_subsubmode != SearchSubSubMode && m_mode != ExMode) {
  5093. editor()->setFocus();
  5094. } else if (text.isEmpty()) {
  5095. // editing cancelled
  5096. handleDefaultKey(Input(Qt::Key_Escape, Qt::NoModifier, QString()));
  5097. editor()->setFocus();
  5098. updateCursorShape();
  5099. } else {
  5100. CommandBuffer &cmdBuf = (m_mode == ExMode) ? g.commandBuffer : g.searchBuffer;
  5101. int pos = qMax(1, cursorPos);
  5102. int anchor = anchorPos == -1 ? pos : qMax(1, anchorPos);
  5103. QString buffer = text;
  5104. // prepend prompt character if missing
  5105. if (!buffer.startsWith(cmdBuf.prompt())) {
  5106. buffer.prepend(cmdBuf.prompt());
  5107. ++pos;
  5108. ++anchor;
  5109. }
  5110. // update command/search buffer
  5111. cmdBuf.setContents(buffer.mid(1), pos - 1, anchor - 1);
  5112. if (pos != cursorPos || anchor != anchorPos || buffer != text)
  5113. emit q->commandBufferChanged(buffer, pos, anchor, 0, q);
  5114. // update search expression
  5115. if (m_subsubmode == SearchSubSubMode) {
  5116. updateFind(false);
  5117. exportSelection();
  5118. }
  5119. }
  5120. }
  5121. // Helper to parse a-z,A-Z,48-57,_
  5122. static int someInt(const QString &str)
  5123. {
  5124. if (str.toInt())
  5125. return str.toInt();
  5126. if (str.size())
  5127. return str.at(0).unicode();
  5128. return 0;
  5129. }
  5130. void FakeVimHandler::Private::setupCharClass()
  5131. {
  5132. for (int i = 0; i < 256; ++i) {
  5133. const QChar c = QChar(QLatin1Char(i));
  5134. m_charClass[i] = c.isSpace() ? 0 : 1;
  5135. }
  5136. const QString conf = config(ConfigIsKeyword).toString();
  5137. foreach (const QString &part, conf.split(QLatin1Char(','))) {
  5138. if (part.contains(QLatin1Char('-'))) {
  5139. const int from = someInt(part.section(QLatin1Char('-'), 0, 0));
  5140. const int to = someInt(part.section(QLatin1Char('-'), 1, 1));
  5141. for (int i = qMax(0, from); i <= qMin(255, to); ++i)
  5142. m_charClass[i] = 2;
  5143. } else {
  5144. m_charClass[qMin(255, someInt(part))] = 2;
  5145. }
  5146. }
  5147. }
  5148. void FakeVimHandler::Private::moveToBoundary(bool simple, bool forward)
  5149. {
  5150. QTextDocument *doc = document();
  5151. QTextCursor tc(doc);
  5152. tc.setPosition(position());
  5153. if (forward ? tc.atBlockEnd() : tc.atBlockStart())
  5154. return;
  5155. QChar c = document()->characterAt(tc.position() + (forward ? -1 : 1));
  5156. int lastClass = tc.atStart() ? -1 : charClass(c, simple);
  5157. QTextCursor::MoveOperation op = forward ? Right : Left;
  5158. while (true) {
  5159. c = doc->characterAt(tc.position());
  5160. int thisClass = charClass(c, simple);
  5161. if (thisClass != lastClass || (forward ? tc.atBlockEnd() : tc.atBlockStart())) {
  5162. if (tc != cursor())
  5163. tc.movePosition(forward ? Left : Right);
  5164. break;
  5165. }
  5166. lastClass = thisClass;
  5167. tc.movePosition(op);
  5168. }
  5169. setPosition(tc.position());
  5170. }
  5171. void FakeVimHandler::Private::moveToNextBoundary(bool end, int count, bool simple, bool forward)
  5172. {
  5173. int repeat = count;
  5174. while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
  5175. setPosition(position() + (forward ? 1 : -1));
  5176. moveToBoundary(simple, forward);
  5177. if (atBoundary(end, simple))
  5178. --repeat;
  5179. }
  5180. }
  5181. void FakeVimHandler::Private::moveToNextBoundaryStart(int count, bool simple, bool forward)
  5182. {
  5183. moveToNextBoundary(false, count, simple, forward);
  5184. }
  5185. void FakeVimHandler::Private::moveToNextBoundaryEnd(int count, bool simple, bool forward)
  5186. {
  5187. moveToNextBoundary(true, count, simple, forward);
  5188. }
  5189. void FakeVimHandler::Private::moveToBoundaryStart(int count, bool simple, bool forward)
  5190. {
  5191. moveToNextBoundaryStart(atBoundary(false, simple) ? count - 1 : count, simple, forward);
  5192. }
  5193. void FakeVimHandler::Private::moveToBoundaryEnd(int count, bool simple, bool forward)
  5194. {
  5195. moveToNextBoundaryEnd(atBoundary(true, simple) ? count - 1 : count, simple, forward);
  5196. }
  5197. void FakeVimHandler::Private::moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines)
  5198. {
  5199. int repeat = count;
  5200. while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
  5201. setPosition(position() + (forward ? 1 : -1));
  5202. moveToBoundary(simple, forward);
  5203. if (atWordBoundary(end, simple) && (emptyLines || !atEmptyLine()) )
  5204. --repeat;
  5205. }
  5206. }
  5207. void FakeVimHandler::Private::moveToNextWordStart(int count, bool simple, bool forward, bool emptyLines)
  5208. {
  5209. moveToNextWord(false, count, simple, forward, emptyLines);
  5210. }
  5211. void FakeVimHandler::Private::moveToNextWordEnd(int count, bool simple, bool forward, bool emptyLines)
  5212. {
  5213. moveToNextWord(true, count, simple, forward, emptyLines);
  5214. }
  5215. void FakeVimHandler::Private::moveToWordStart(int count, bool simple, bool forward, bool emptyLines)
  5216. {
  5217. moveToNextWordStart(atWordStart(simple) ? count - 1 : count, simple, forward, emptyLines);
  5218. }
  5219. void FakeVimHandler::Private::moveToWordEnd(int count, bool simple, bool forward, bool emptyLines)
  5220. {
  5221. moveToNextWordEnd(atWordEnd(simple) ? count - 1 : count, simple, forward, emptyLines);
  5222. }
  5223. bool FakeVimHandler::Private::handleFfTt(QString key)
  5224. {
  5225. int key0 = key.size() == 1 ? key.at(0).unicode() : 0;
  5226. int oldPos = position();
  5227. // m_subsubmode \in { 'f', 'F', 't', 'T' }
  5228. bool forward = m_subsubdata.is('f') || m_subsubdata.is('t');
  5229. int repeat = count();
  5230. QTextDocument *doc = document();
  5231. int n = block().position();
  5232. if (forward)
  5233. n += block().length();
  5234. int pos = position();
  5235. while (pos != n) {
  5236. pos += forward ? 1 : -1;
  5237. if (pos == n)
  5238. break;
  5239. int uc = doc->characterAt(pos).unicode();
  5240. if (uc == ParagraphSeparator)
  5241. break;
  5242. if (uc == key0)
  5243. --repeat;
  5244. if (repeat == 0) {
  5245. if (m_subsubdata.is('t'))
  5246. --pos;
  5247. else if (m_subsubdata.is('T'))
  5248. ++pos;
  5249. if (forward)
  5250. moveRight(pos - position());
  5251. else
  5252. moveLeft(position() - pos);
  5253. break;
  5254. }
  5255. }
  5256. if (repeat == 0) {
  5257. setTargetColumn();
  5258. return true;
  5259. }
  5260. setPosition(oldPos);
  5261. return false;
  5262. }
  5263. void FakeVimHandler::Private::moveToMatchingParanthesis()
  5264. {
  5265. bool moved = false;
  5266. bool forward = false;
  5267. const int anc = anchor();
  5268. QTextCursor tc = cursor();
  5269. emit q->moveToMatchingParenthesis(&moved, &forward, &tc);
  5270. if (moved && forward)
  5271. tc.movePosition(Left, KeepAnchor, 1);
  5272. setAnchorAndPosition(anc, tc.position());
  5273. setTargetColumn();
  5274. }
  5275. int FakeVimHandler::Private::cursorLineOnScreen() const
  5276. {
  5277. if (!editor())
  5278. return 0;
  5279. QRect rect = EDITOR(cursorRect());
  5280. return rect.y() / rect.height();
  5281. }
  5282. int FakeVimHandler::Private::linesOnScreen() const
  5283. {
  5284. if (!editor())
  5285. return 1;
  5286. QRect rect = EDITOR(cursorRect());
  5287. return EDITOR(height()) / rect.height();
  5288. }
  5289. int FakeVimHandler::Private::columnsOnScreen() const
  5290. {
  5291. if (!editor())
  5292. return 1;
  5293. QRect rect = EDITOR(cursorRect());
  5294. // qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
  5295. return EDITOR(width()) / rect.width();
  5296. }
  5297. int FakeVimHandler::Private::cursorLine() const
  5298. {
  5299. return lineForPosition(position()) - 1;
  5300. }
  5301. int FakeVimHandler::Private::cursorBlockNumber() const
  5302. {
  5303. return document()->findBlock(qMin(anchor(), position())).blockNumber();
  5304. }
  5305. int FakeVimHandler::Private::physicalCursorColumn() const
  5306. {
  5307. return position() - block().position();
  5308. }
  5309. int FakeVimHandler::Private::physicalToLogicalColumn
  5310. (const int physical, const QString &line) const
  5311. {
  5312. const int ts = config(ConfigTabStop).toInt();
  5313. int p = 0;
  5314. int logical = 0;
  5315. while (p < physical) {
  5316. QChar c = line.at(p);
  5317. //if (c == QLatin1Char(' '))
  5318. // ++logical;
  5319. //else
  5320. if (c == QLatin1Char('\t'))
  5321. logical += ts - logical % ts;
  5322. else
  5323. ++logical;
  5324. //break;
  5325. ++p;
  5326. }
  5327. return logical;
  5328. }
  5329. int FakeVimHandler::Private::logicalToPhysicalColumn
  5330. (const int logical, const QString &line) const
  5331. {
  5332. const int ts = config(ConfigTabStop).toInt();
  5333. int physical = 0;
  5334. for (int l = 0; l < logical && physical < line.size(); ++physical) {
  5335. QChar c = line.at(physical);
  5336. if (c == QLatin1Char('\t'))
  5337. l += ts - l % ts;
  5338. else
  5339. ++l;
  5340. }
  5341. return physical;
  5342. }
  5343. int FakeVimHandler::Private::logicalCursorColumn() const
  5344. {
  5345. const int physical = physicalCursorColumn();
  5346. const QString line = block().text();
  5347. return physicalToLogicalColumn(physical, line);
  5348. }
  5349. Column FakeVimHandler::Private::cursorColumn() const
  5350. {
  5351. return Column(physicalCursorColumn(), logicalCursorColumn());
  5352. }
  5353. int FakeVimHandler::Private::linesInDocument() const
  5354. {
  5355. if (cursor().isNull())
  5356. return 0;
  5357. const int count = document()->blockCount();
  5358. // Qt inserts an empty line if the last character is a '\n',
  5359. // but that's not how vi does it.
  5360. return document()->lastBlock().length() <= 1 ? count - 1 : count;
  5361. }
  5362. void FakeVimHandler::Private::scrollToLine(int line)
  5363. {
  5364. // FIXME: works only for QPlainTextEdit
  5365. QScrollBar *scrollBar = EDITOR(verticalScrollBar());
  5366. //qDebug() << "SCROLL: " << scrollBar->value() << line;
  5367. scrollBar->setValue(line);
  5368. //QTC_CHECK(firstVisibleLine() == line);
  5369. }
  5370. int FakeVimHandler::Private::firstVisibleLine() const
  5371. {
  5372. QScrollBar *scrollBar = EDITOR(verticalScrollBar());
  5373. if (0 && scrollBar->value() != cursorLine() - cursorLineOnScreen()) {
  5374. qDebug() << "SCROLLBAR: " << scrollBar->value()
  5375. << "CURSORLINE IN DOC" << cursorLine()
  5376. << "CURSORLINE ON SCREEN" << cursorLineOnScreen();
  5377. }
  5378. //return scrollBar->value();
  5379. return cursorLine() - cursorLineOnScreen();
  5380. }
  5381. void FakeVimHandler::Private::scrollUp(int count)
  5382. {
  5383. scrollToLine(cursorLine() - cursorLineOnScreen() - count);
  5384. }
  5385. void FakeVimHandler::Private::alignViewportToCursor(AlignmentFlag align, int line,
  5386. bool moveToNonBlank)
  5387. {
  5388. if (line > 0)
  5389. setPosition(firstPositionInLine(line));
  5390. if (moveToNonBlank)
  5391. moveToFirstNonBlankOnLine();
  5392. if (align == Qt::AlignTop)
  5393. scrollUp(- cursorLineOnScreen());
  5394. else if (align == Qt::AlignVCenter)
  5395. scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
  5396. else if (align == Qt::AlignBottom)
  5397. scrollUp(linesOnScreen() - cursorLineOnScreen());
  5398. }
  5399. void FakeVimHandler::Private::setCursorPosition(const CursorPosition &p)
  5400. {
  5401. const int firstLine = firstVisibleLine();
  5402. const int firstBlock = document()->findBlockByLineNumber(firstLine).blockNumber();
  5403. const int lastBlock =
  5404. document()->findBlockByLineNumber(firstLine + linesOnScreen() - 2).blockNumber();
  5405. bool isLineVisible = firstBlock <= p.line && p.line <= lastBlock;
  5406. QTextCursor tc = cursor();
  5407. setCursorPosition(&tc, p);
  5408. setCursor(tc);
  5409. if (!isLineVisible)
  5410. alignViewportToCursor(Qt::AlignVCenter);
  5411. }
  5412. void FakeVimHandler::Private::setCursorPosition(QTextCursor *tc, const CursorPosition &p)
  5413. {
  5414. const int line = qMin(document()->blockCount() - 1, p.line);
  5415. QTextBlock block = document()->findBlockByNumber(line);
  5416. const int column = qMin(p.column, block.length() - 1);
  5417. tc->setPosition(block.position() + column, KeepAnchor);
  5418. }
  5419. int FakeVimHandler::Private::lastPositionInDocument(bool ignoreMode) const
  5420. {
  5421. return document()->characterCount()
  5422. - (ignoreMode || isVisualMode() || m_mode == InsertMode || m_mode == ReplaceMode ? 1 : 2);
  5423. }
  5424. QString FakeVimHandler::Private::selectText(const Range &range) const
  5425. {
  5426. if (range.rangemode == RangeCharMode) {
  5427. QTextCursor tc(document());
  5428. tc.setPosition(range.beginPos, MoveAnchor);
  5429. tc.setPosition(range.endPos, KeepAnchor);
  5430. return tc.selection().toPlainText();
  5431. }
  5432. if (range.rangemode == RangeLineMode) {
  5433. QTextCursor tc(document());
  5434. int firstPos = firstPositionInLine(lineForPosition(range.beginPos));
  5435. int lastLine = lineForPosition(range.endPos);
  5436. bool endOfDoc = lastLine == lineNumber(document()->lastBlock());
  5437. int lastPos = endOfDoc ? lastPositionInDocument(true) : firstPositionInLine(lastLine + 1);
  5438. tc.setPosition(firstPos, MoveAnchor);
  5439. tc.setPosition(lastPos, KeepAnchor);
  5440. return tc.selection().toPlainText() + _(endOfDoc? "\n" : "");
  5441. }
  5442. // FIXME: Performance?
  5443. int beginLine = lineForPosition(range.beginPos);
  5444. int endLine = lineForPosition(range.endPos);
  5445. int beginColumn = 0;
  5446. int endColumn = INT_MAX;
  5447. if (range.rangemode == RangeBlockMode) {
  5448. int column1 = range.beginPos - firstPositionInLine(beginLine);
  5449. int column2 = range.endPos - firstPositionInLine(endLine);
  5450. beginColumn = qMin(column1, column2);
  5451. endColumn = qMax(column1, column2);
  5452. }
  5453. int len = endColumn - beginColumn + 1;
  5454. QString contents;
  5455. QTextBlock block = document()->findBlockByLineNumber(beginLine - 1);
  5456. for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
  5457. QString line = block.text();
  5458. if (range.rangemode == RangeBlockMode) {
  5459. line = line.mid(beginColumn, len);
  5460. if (line.size() < len)
  5461. line += QString(len - line.size(), QLatin1Char(' '));
  5462. }
  5463. contents += line;
  5464. if (!contents.endsWith(QLatin1Char('\n')))
  5465. contents += QLatin1Char('\n');
  5466. block = block.next();
  5467. }
  5468. //qDebug() << "SELECTED: " << contents;
  5469. return contents;
  5470. }
  5471. void FakeVimHandler::Private::yankText(const Range &range, int reg)
  5472. {
  5473. setRegister(reg, selectText(range), range.rangemode);
  5474. const int lines = document()->findBlock(range.endPos).blockNumber()
  5475. - document()->findBlock(range.beginPos).blockNumber() + 1;
  5476. if (lines > 2)
  5477. showMessage(MessageInfo, FakeVimHandler::tr("%1 lines yanked").arg(lines));
  5478. }
  5479. void FakeVimHandler::Private::transformText(const Range &range,
  5480. Transformation transformFunc, const QVariant &extra)
  5481. {
  5482. QTextCursor tc = cursor();
  5483. int posAfter = range.beginPos;
  5484. switch (range.rangemode) {
  5485. case RangeCharMode: {
  5486. // This can span multiple lines.
  5487. beginEditBlock();
  5488. tc.setPosition(range.beginPos, MoveAnchor);
  5489. tc.setPosition(range.endPos, KeepAnchor);
  5490. TransformationData td(tc.selectedText(), extra);
  5491. (this->*transformFunc)(&td);
  5492. tc.insertText(td.to);
  5493. endEditBlock();
  5494. break;
  5495. }
  5496. case RangeLineMode:
  5497. case RangeLineModeExclusive: {
  5498. beginEditBlock();
  5499. tc.setPosition(range.beginPos, MoveAnchor);
  5500. tc.movePosition(StartOfLine, MoveAnchor);
  5501. tc.setPosition(range.endPos, KeepAnchor);
  5502. tc.movePosition(EndOfLine, KeepAnchor);
  5503. if (range.rangemode != RangeLineModeExclusive) {
  5504. // make sure that complete lines are removed
  5505. // - also at the beginning and at the end of the document
  5506. if (tc.atEnd()) {
  5507. tc.setPosition(range.beginPos, MoveAnchor);
  5508. tc.movePosition(StartOfLine, MoveAnchor);
  5509. if (!tc.atStart()) {
  5510. // also remove first line if it is the only one
  5511. tc.movePosition(Left, MoveAnchor, 1);
  5512. tc.movePosition(EndOfLine, MoveAnchor, 1);
  5513. }
  5514. tc.setPosition(range.endPos, KeepAnchor);
  5515. tc.movePosition(EndOfLine, KeepAnchor);
  5516. } else {
  5517. tc.movePosition(Right, KeepAnchor, 1);
  5518. }
  5519. }
  5520. TransformationData td(tc.selectedText(), extra);
  5521. (this->*transformFunc)(&td);
  5522. posAfter = tc.anchor();
  5523. tc.insertText(td.to);
  5524. endEditBlock();
  5525. break;
  5526. }
  5527. case RangeBlockAndTailMode:
  5528. case RangeBlockMode: {
  5529. int beginLine = lineForPosition(range.beginPos);
  5530. int endLine = lineForPosition(range.endPos);
  5531. int column1 = range.beginPos - firstPositionInLine(beginLine);
  5532. int column2 = range.endPos - firstPositionInLine(endLine);
  5533. int beginColumn = qMin(column1, column2);
  5534. int endColumn = qMax(column1, column2);
  5535. if (range.rangemode == RangeBlockAndTailMode)
  5536. endColumn = INT_MAX - 1;
  5537. QTextBlock block = document()->findBlockByLineNumber(endLine - 1);
  5538. beginEditBlock();
  5539. for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
  5540. int bCol = qMin(beginColumn, block.length() - 1);
  5541. int eCol = qMin(endColumn + 1, block.length() - 1);
  5542. tc.setPosition(block.position() + bCol, MoveAnchor);
  5543. tc.setPosition(block.position() + eCol, KeepAnchor);
  5544. TransformationData td(tc.selectedText(), extra);
  5545. (this->*transformFunc)(&td);
  5546. tc.insertText(td.to);
  5547. block = block.previous();
  5548. }
  5549. endEditBlock();
  5550. break;
  5551. }
  5552. }
  5553. setPosition(posAfter);
  5554. setTargetColumn();
  5555. }
  5556. void FakeVimHandler::Private::insertText(const Register &reg)
  5557. {
  5558. QTC_ASSERT(reg.rangemode == RangeCharMode,
  5559. qDebug() << "WRONG INSERT MODE: " << reg.rangemode; return);
  5560. setAnchor();
  5561. cursor().insertText(reg.contents);
  5562. //dump("AFTER INSERT");
  5563. }
  5564. void FakeVimHandler::Private::removeText(const Range &range)
  5565. {
  5566. //qDebug() << "REMOVE: " << range;
  5567. transformText(range, &FakeVimHandler::Private::removeTransform);
  5568. }
  5569. void FakeVimHandler::Private::removeTransform(TransformationData *td)
  5570. {
  5571. Q_UNUSED(td);
  5572. }
  5573. void FakeVimHandler::Private::downCase(const Range &range)
  5574. {
  5575. transformText(range, &FakeVimHandler::Private::downCaseTransform);
  5576. }
  5577. void FakeVimHandler::Private::downCaseTransform(TransformationData *td)
  5578. {
  5579. td->to = td->from.toLower();
  5580. }
  5581. void FakeVimHandler::Private::upCase(const Range &range)
  5582. {
  5583. transformText(range, &FakeVimHandler::Private::upCaseTransform);
  5584. }
  5585. void FakeVimHandler::Private::upCaseTransform(TransformationData *td)
  5586. {
  5587. td->to = td->from.toUpper();
  5588. }
  5589. void FakeVimHandler::Private::invertCase(const Range &range)
  5590. {
  5591. transformText(range, &FakeVimHandler::Private::invertCaseTransform);
  5592. }
  5593. void FakeVimHandler::Private::invertCaseTransform(TransformationData *td)
  5594. {
  5595. foreach (QChar c, td->from)
  5596. td->to += c.isUpper() ? c.toLower() : c.toUpper();
  5597. }
  5598. void FakeVimHandler::Private::replaceText(const Range &range, const QString &str)
  5599. {
  5600. Transformation tr = &FakeVimHandler::Private::replaceByStringTransform;
  5601. transformText(range, tr, str);
  5602. }
  5603. void FakeVimHandler::Private::replaceByStringTransform(TransformationData *td)
  5604. {
  5605. td->to = td->extraData.toString();
  5606. }
  5607. void FakeVimHandler::Private::replaceByCharTransform(TransformationData *td)
  5608. {
  5609. // Replace each character but preserve lines.
  5610. const int len = td->from.size();
  5611. td->to = QString(len, td->extraData.toChar());
  5612. for (int i = 0; i < len; ++i) {
  5613. if (td->from.at(i) == ParagraphSeparator)
  5614. td->to[i] = ParagraphSeparator;
  5615. }
  5616. }
  5617. void FakeVimHandler::Private::pasteText(bool afterCursor)
  5618. {
  5619. const QString text = registerContents(m_register);
  5620. const RangeMode rangeMode = registerRangeMode(m_register);
  5621. beginEditBlock();
  5622. // In visual mode paste text only inside selection.
  5623. bool pasteAfter = isVisualMode() ? false : afterCursor;
  5624. bool visualCharMode = isVisualCharMode();
  5625. if (visualCharMode) {
  5626. leaveVisualMode();
  5627. m_rangemode = RangeCharMode;
  5628. Range range = currentRange();
  5629. range.endPos++;
  5630. yankText(range, m_register);
  5631. removeText(range);
  5632. } else if (isVisualLineMode()) {
  5633. leaveVisualMode();
  5634. m_rangemode = RangeLineMode;
  5635. Range range = currentRange();
  5636. range.endPos++;
  5637. yankText(range, m_register);
  5638. removeText(range);
  5639. handleStartOfLine();
  5640. } else if (isVisualBlockMode()) {
  5641. leaveVisualMode();
  5642. m_rangemode = RangeBlockMode;
  5643. Range range = currentRange();
  5644. yankText(range, m_register);
  5645. removeText(range);
  5646. setPosition(qMin(position(), anchor()));
  5647. }
  5648. switch (rangeMode) {
  5649. case RangeCharMode: {
  5650. m_targetColumn = 0;
  5651. const int pos = position() + 1;
  5652. if (pasteAfter && rightDist() > 0)
  5653. moveRight();
  5654. insertText(text.repeated(count()));
  5655. if (text.contains(QLatin1Char('\n')))
  5656. setPosition(pos);
  5657. else
  5658. moveLeft();
  5659. break;
  5660. }
  5661. case RangeLineMode:
  5662. case RangeLineModeExclusive: {
  5663. QTextCursor tc = cursor();
  5664. if (visualCharMode)
  5665. tc.insertBlock();
  5666. else
  5667. moveToStartOfLine();
  5668. m_targetColumn = 0;
  5669. bool lastLine = false;
  5670. if (pasteAfter) {
  5671. lastLine = document()->lastBlock() == this->block();
  5672. if (lastLine) {
  5673. tc.movePosition(EndOfLine, MoveAnchor);
  5674. tc.insertBlock();
  5675. }
  5676. moveDown();
  5677. }
  5678. const int pos = position();
  5679. if (lastLine)
  5680. insertText(text.repeated(count()).left(text.size() * count() - 1));
  5681. else
  5682. insertText(text.repeated(count()));
  5683. setPosition(pos);
  5684. moveToFirstNonBlankOnLine();
  5685. break;
  5686. }
  5687. case RangeBlockAndTailMode:
  5688. case RangeBlockMode: {
  5689. const int pos = position();
  5690. if (pasteAfter && rightDist() > 0)
  5691. moveRight();
  5692. QTextCursor tc = cursor();
  5693. const int col = tc.columnNumber();
  5694. QTextBlock block = tc.block();
  5695. const QStringList lines = text.split(QLatin1Char('\n'));
  5696. for (int i = 0; i < lines.size() - 1; ++i) {
  5697. if (!block.isValid()) {
  5698. tc.movePosition(EndOfDocument);
  5699. tc.insertBlock();
  5700. block = tc.block();
  5701. }
  5702. // resize line
  5703. int length = block.length();
  5704. int begin = block.position();
  5705. if (col >= length) {
  5706. tc.setPosition(begin + length - 1);
  5707. tc.insertText(QString(col - length + 1, QLatin1Char(' ')));
  5708. } else {
  5709. tc.setPosition(begin + col);
  5710. }
  5711. // insert text
  5712. const QString line = lines.at(i).repeated(count());
  5713. tc.insertText(line);
  5714. // next line
  5715. block = block.next();
  5716. }
  5717. setPosition(pos);
  5718. if (pasteAfter)
  5719. moveRight();
  5720. break;
  5721. }
  5722. }
  5723. endEditBlock();
  5724. }
  5725. void FakeVimHandler::Private::joinLines(int count, bool preserveSpace)
  5726. {
  5727. int pos = position();
  5728. const int blockNumber = cursor().blockNumber();
  5729. for (int i = qMax(count - 2, 0); i >= 0 && blockNumber < document()->blockCount(); --i) {
  5730. moveBehindEndOfLine();
  5731. pos = position();
  5732. setAnchor();
  5733. moveRight();
  5734. if (preserveSpace) {
  5735. removeText(currentRange());
  5736. } else {
  5737. while (characterAtCursor() == QLatin1Char(' ') || characterAtCursor() == QLatin1Char('\t'))
  5738. moveRight();
  5739. cursor().insertText(QString(QLatin1Char(' ')));
  5740. }
  5741. }
  5742. setPosition(pos);
  5743. }
  5744. QString FakeVimHandler::Private::lineContents(int line) const
  5745. {
  5746. return document()->findBlockByLineNumber(line - 1).text();
  5747. }
  5748. void FakeVimHandler::Private::setLineContents(int line, const QString &contents)
  5749. {
  5750. QTextBlock block = document()->findBlockByLineNumber(line - 1);
  5751. QTextCursor tc = cursor();
  5752. const int begin = block.position();
  5753. const int len = block.length();
  5754. tc.setPosition(begin);
  5755. tc.setPosition(begin + len - 1, KeepAnchor);
  5756. tc.insertText(contents);
  5757. }
  5758. int FakeVimHandler::Private::blockBoundary(const QString &left,
  5759. const QString &right, bool closing, int count) const
  5760. {
  5761. const QString &begin = closing ? left : right;
  5762. const QString &end = closing ? right : left;
  5763. // shift cursor if it is already on opening/closing string
  5764. QTextCursor tc1 = cursor();
  5765. int pos = tc1.position();
  5766. int max = document()->characterCount();
  5767. int sz = left.size();
  5768. int from = qMax(pos - sz + 1, 0);
  5769. int to = qMin(pos + sz, max);
  5770. tc1.setPosition(from);
  5771. tc1.setPosition(to, KeepAnchor);
  5772. int i = tc1.selectedText().indexOf(left);
  5773. if (i != -1) {
  5774. // - on opening string:
  5775. tc1.setPosition(from + i + sz);
  5776. } else {
  5777. sz = right.size();
  5778. from = qMax(pos - sz + 1, 0);
  5779. to = qMin(pos + sz, max);
  5780. tc1.setPosition(from);
  5781. tc1.setPosition(to, KeepAnchor);
  5782. i = tc1.selectedText().indexOf(right);
  5783. if (i != -1) {
  5784. // - on closing string:
  5785. tc1.setPosition(from + i);
  5786. } else {
  5787. tc1 = cursor();
  5788. }
  5789. }
  5790. QTextCursor tc2 = tc1;
  5791. QTextDocument::FindFlags flags(closing ? 0 : QTextDocument::FindBackward);
  5792. int level = 0;
  5793. int counter = 0;
  5794. while (true) {
  5795. tc2 = document()->find(end, tc2, flags);
  5796. if (tc2.isNull())
  5797. return -1;
  5798. if (!tc1.isNull())
  5799. tc1 = document()->find(begin, tc1, flags);
  5800. while (!tc1.isNull() && (closing ? (tc1 < tc2) : (tc2 < tc1))) {
  5801. ++level;
  5802. tc1 = document()->find(begin, tc1, flags);
  5803. }
  5804. while (level > 0
  5805. && (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
  5806. --level;
  5807. tc2 = document()->find(end, tc2, flags);
  5808. if (tc2.isNull())
  5809. return -1;
  5810. }
  5811. if (level == 0
  5812. && (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
  5813. ++counter;
  5814. if (counter >= count)
  5815. break;
  5816. }
  5817. }
  5818. return tc2.position() - end.size();
  5819. }
  5820. int FakeVimHandler::Private::lineNumber(const QTextBlock &block) const
  5821. {
  5822. if (block.isVisible())
  5823. return block.firstLineNumber() + 1;
  5824. // Folded block has line number of the nearest previous visible line.
  5825. QTextBlock block2 = block;
  5826. while (block2.isValid() && !block2.isVisible())
  5827. block2 = block2.previous();
  5828. return block2.firstLineNumber() + 1;
  5829. }
  5830. int FakeVimHandler::Private::firstPositionInLine(int line, bool onlyVisibleLines) const
  5831. {
  5832. QTextBlock block = onlyVisibleLines ? document()->findBlockByLineNumber(line - 1)
  5833. : document()->findBlockByNumber(line - 1);
  5834. return block.position();
  5835. }
  5836. int FakeVimHandler::Private::lastPositionInLine(int line, bool onlyVisibleLines) const
  5837. {
  5838. QTextBlock block;
  5839. if (onlyVisibleLines) {
  5840. // respect folds
  5841. block = document()->findBlockByLineNumber(line);
  5842. if (block.isValid()) {
  5843. if (line > 0)
  5844. block = block.previous();
  5845. } else {
  5846. block = document()->lastBlock();
  5847. }
  5848. } else {
  5849. block = document()->findBlockByNumber(line - 1);
  5850. }
  5851. const int position = block.position() + block.length() - 1;
  5852. if (block.length() > 1 && !isVisualMode() && m_mode != InsertMode && m_mode != ReplaceMode)
  5853. return position - 1;
  5854. return position;
  5855. }
  5856. int FakeVimHandler::Private::lineForPosition(int pos) const
  5857. {
  5858. QTextBlock block = document()->findBlock(pos);
  5859. return lineNumber(block);
  5860. }
  5861. void FakeVimHandler::Private::toggleVisualMode(VisualMode visualMode)
  5862. {
  5863. if (isVisualMode()) {
  5864. leaveVisualMode();
  5865. } else {
  5866. m_positionPastEnd = false;
  5867. m_anchorPastEnd = false;
  5868. m_visualMode = visualMode;
  5869. m_lastVisualMode = visualMode;
  5870. const int pos = position();
  5871. setAnchorAndPosition(pos, pos);
  5872. updateMiniBuffer();
  5873. }
  5874. }
  5875. void FakeVimHandler::Private::leaveVisualMode()
  5876. {
  5877. if (!isVisualMode())
  5878. return;
  5879. setMark(QLatin1Char('<'), mark(QLatin1Char('<')).position);
  5880. setMark(QLatin1Char('>'), mark(QLatin1Char('>')).position);
  5881. m_lastVisualModeInverted = anchor() > position();
  5882. if (isVisualLineMode())
  5883. m_movetype = MoveLineWise;
  5884. else if (isVisualCharMode())
  5885. m_movetype = MoveInclusive;
  5886. m_visualMode = NoVisualMode;
  5887. updateMiniBuffer();
  5888. }
  5889. QWidget *FakeVimHandler::Private::editor() const
  5890. {
  5891. return m_textedit
  5892. ? static_cast<QWidget *>(m_textedit)
  5893. : static_cast<QWidget *>(m_plaintextedit);
  5894. }
  5895. void FakeVimHandler::Private::joinPreviousEditBlock()
  5896. {
  5897. UNDO_DEBUG("JOIN");
  5898. if (m_breakEditBlock) {
  5899. beginEditBlock();
  5900. } else {
  5901. if (m_editBlockLevel == 0)
  5902. m_cursor = cursor();
  5903. ++m_editBlockLevel;
  5904. cursor().joinPreviousEditBlock();
  5905. }
  5906. }
  5907. void FakeVimHandler::Private::beginEditBlock(bool rememberPosition)
  5908. {
  5909. UNDO_DEBUG("BEGIN EDIT BLOCK");
  5910. if (m_editBlockLevel == 0)
  5911. m_cursor = cursor();
  5912. ++m_editBlockLevel;
  5913. cursor().beginEditBlock();
  5914. if (rememberPosition)
  5915. setUndoPosition(false);
  5916. m_breakEditBlock = false;
  5917. }
  5918. void FakeVimHandler::Private::endEditBlock()
  5919. {
  5920. UNDO_DEBUG("END EDIT BLOCK");
  5921. QTC_ASSERT(m_editBlockLevel > 0,
  5922. qDebug() << "beginEditBlock() not called before endEditBlock()!"; return);
  5923. --m_editBlockLevel;
  5924. cursor().endEditBlock();
  5925. if (m_editBlockLevel == 0)
  5926. setCursor(m_cursor);
  5927. }
  5928. char FakeVimHandler::Private::currentModeCode() const
  5929. {
  5930. if (m_submode != NoSubMode)
  5931. return ' ';
  5932. else if (m_mode == ExMode)
  5933. return 'c';
  5934. else if (isVisualMode())
  5935. return 'v';
  5936. else if (m_mode == CommandMode)
  5937. return 'n';
  5938. else
  5939. return 'i';
  5940. }
  5941. void FakeVimHandler::Private::undoRedo(bool undo)
  5942. {
  5943. // FIXME: That's only an approximaxtion. The real solution might
  5944. // be to store marks and old userData with QTextBlock setUserData
  5945. // and retrieve them afterward.
  5946. QStack<State> &stack = undo ? m_undo : m_redo;
  5947. QStack<State> &stack2 = undo ? m_redo : m_undo;
  5948. CursorPosition lastPos(cursor());
  5949. const int current = revision();
  5950. if (undo)
  5951. EDITOR(undo());
  5952. else
  5953. EDITOR(redo());
  5954. const int rev = revision();
  5955. // rewind/forward to last saved revision
  5956. while (!stack.empty() && stack.top().revision > rev)
  5957. stack.pop();
  5958. if (current == rev) {
  5959. const QString msg = undo ? FakeVimHandler::tr("Already at oldest change")
  5960. : FakeVimHandler::tr("Already at newest change");
  5961. showMessage(MessageInfo, msg);
  5962. return;
  5963. }
  5964. clearMessage();
  5965. if (!stack.empty()) {
  5966. State &state = stack.top();
  5967. if (state.revision == rev) {
  5968. m_lastChangePosition = state.position;
  5969. Marks marks = m_marks;
  5970. marks.swap(state.marks);
  5971. updateMarks(marks);
  5972. m_lastVisualMode = state.lastVisualMode;
  5973. m_lastVisualModeInverted = state.lastVisualModeInverted;
  5974. setMark(QLatin1Char('\''), lastPos);
  5975. setCursorPosition(m_lastChangePosition);
  5976. setAnchor();
  5977. state.revision = current;
  5978. stack2.push(stack.pop());
  5979. }
  5980. }
  5981. setTargetColumn();
  5982. if (atEndOfLine())
  5983. moveLeft();
  5984. }
  5985. void FakeVimHandler::Private::undo()
  5986. {
  5987. undoRedo(true);
  5988. }
  5989. void FakeVimHandler::Private::redo()
  5990. {
  5991. undoRedo(false);
  5992. }
  5993. void FakeVimHandler::Private::updateCursorShape()
  5994. {
  5995. bool thinCursor = m_mode == ExMode
  5996. || m_subsubmode == SearchSubSubMode
  5997. || m_mode == InsertMode
  5998. || isVisualMode()
  5999. || cursor().hasSelection();
  6000. EDITOR(setOverwriteMode(!thinCursor));
  6001. }
  6002. void FakeVimHandler::Private::enterReplaceMode()
  6003. {
  6004. m_mode = ReplaceMode;
  6005. m_submode = NoSubMode;
  6006. m_subsubmode = NoSubSubMode;
  6007. m_lastInsertion.clear();
  6008. m_oldPosition = position();
  6009. g.returnToMode = ReplaceMode;
  6010. }
  6011. void FakeVimHandler::Private::enterInsertMode()
  6012. {
  6013. m_mode = InsertMode;
  6014. m_submode = NoSubMode;
  6015. m_subsubmode = NoSubSubMode;
  6016. m_lastInsertion.clear();
  6017. m_oldPosition = position();
  6018. g.returnToMode = InsertMode;
  6019. }
  6020. void FakeVimHandler::Private::initVisualBlockInsertMode(QChar command)
  6021. {
  6022. m_visualBlockInsert = true;
  6023. setDotCommand(visualDotCommand() + QString::number(count()) + command);
  6024. leaveVisualMode();
  6025. const CursorPosition lastAnchor = mark(QLatin1Char('<')).position;
  6026. const CursorPosition lastPosition = mark(QLatin1Char('>')).position;
  6027. CursorPosition pos(lastAnchor.line,
  6028. command == QLatin1Char('A') ? qMax(lastPosition.column, lastAnchor.column)
  6029. : qMin(lastPosition.column, lastAnchor.column));
  6030. if (command == QLatin1Char('s')) {
  6031. Range range(position(), anchor(), RangeBlockMode);
  6032. yankText(range, m_register);
  6033. removeText(range);
  6034. }
  6035. setCursorPosition(pos);
  6036. }
  6037. void FakeVimHandler::Private::enterCommandMode(Mode returnToMode)
  6038. {
  6039. if (atEndOfLine())
  6040. moveLeft();
  6041. m_mode = CommandMode;
  6042. m_submode = NoSubMode;
  6043. m_subsubmode = NoSubSubMode;
  6044. g.returnToMode = returnToMode;
  6045. }
  6046. void FakeVimHandler::Private::enterExMode(const QString &contents)
  6047. {
  6048. g.currentMessage.clear();
  6049. if (isVisualMode())
  6050. g.commandBuffer.setContents(QString::fromLatin1("'<,'>") + contents, contents.size() + 5);
  6051. else
  6052. g.commandBuffer.setContents(contents, contents.size());
  6053. m_mode = ExMode;
  6054. m_submode = NoSubMode;
  6055. m_subsubmode = NoSubSubMode;
  6056. }
  6057. void FakeVimHandler::Private::recordJump(int position)
  6058. {
  6059. CursorPosition pos = position >= 0 ? CursorPosition(document(), position)
  6060. : CursorPosition(cursor());
  6061. setMark(QLatin1Char('\''), pos);
  6062. if (m_jumpListUndo.isEmpty() || m_jumpListUndo.top() != pos)
  6063. m_jumpListUndo.push(pos);
  6064. m_jumpListRedo.clear();
  6065. UNDO_DEBUG("jumps: " << m_jumpListUndo);
  6066. }
  6067. void FakeVimHandler::Private::jump(int distance)
  6068. {
  6069. QStack<CursorPosition> &from = (distance > 0) ? m_jumpListRedo : m_jumpListUndo;
  6070. QStack<CursorPosition> &to = (distance > 0) ? m_jumpListUndo : m_jumpListRedo;
  6071. int len = qMin(qAbs(distance), from.size());
  6072. CursorPosition m(cursor());
  6073. setMark(QLatin1Char('\''), m);
  6074. for (int i = 0; i < len; ++i) {
  6075. to.push(m);
  6076. setCursorPosition(from.top());
  6077. from.pop();
  6078. }
  6079. }
  6080. Column FakeVimHandler::Private::indentation(const QString &line) const
  6081. {
  6082. int ts = config(ConfigTabStop).toInt();
  6083. int physical = 0;
  6084. int logical = 0;
  6085. int n = line.size();
  6086. while (physical < n) {
  6087. QChar c = line.at(physical);
  6088. if (c == QLatin1Char(' '))
  6089. ++logical;
  6090. else if (c == QLatin1Char('\t'))
  6091. logical += ts - logical % ts;
  6092. else
  6093. break;
  6094. ++physical;
  6095. }
  6096. return Column(physical, logical);
  6097. }
  6098. QString FakeVimHandler::Private::tabExpand(int n) const
  6099. {
  6100. int ts = config(ConfigTabStop).toInt();
  6101. if (hasConfig(ConfigExpandTab) || ts < 1)
  6102. return QString(n, QLatin1Char(' '));
  6103. return QString(n / ts, QLatin1Char('\t'))
  6104. + QString(n % ts, QLatin1Char(' '));
  6105. }
  6106. void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
  6107. {
  6108. if (!hasConfig(ConfigAutoIndent) && !hasConfig(ConfigSmartIndent))
  6109. return;
  6110. if (hasConfig(ConfigSmartIndent)) {
  6111. QTextBlock bl = block();
  6112. Range range(bl.position(), bl.position());
  6113. const int oldSize = bl.text().size();
  6114. indentText(range, QLatin1Char('\n'));
  6115. m_justAutoIndented = bl.text().size() - oldSize;
  6116. } else {
  6117. QTextBlock bl = goingDown ? block().previous() : block().next();
  6118. QString text = bl.text();
  6119. int pos = 0;
  6120. int n = text.size();
  6121. while (pos < n && text.at(pos).isSpace())
  6122. ++pos;
  6123. text.truncate(pos);
  6124. // FIXME: handle 'smartindent' and 'cindent'
  6125. insertText(text);
  6126. m_justAutoIndented = text.size();
  6127. }
  6128. }
  6129. bool FakeVimHandler::Private::removeAutomaticIndentation()
  6130. {
  6131. if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
  6132. return false;
  6133. /*
  6134. m_tc.movePosition(StartOfLine, KeepAnchor);
  6135. m_tc.removeSelectedText();
  6136. m_lastInsertion.chop(m_justAutoIndented);
  6137. */
  6138. m_justAutoIndented = 0;
  6139. return true;
  6140. }
  6141. void FakeVimHandler::Private::handleStartOfLine()
  6142. {
  6143. if (hasConfig(ConfigStartOfLine))
  6144. moveToFirstNonBlankOnLine();
  6145. }
  6146. void FakeVimHandler::Private::replay(const QString &command)
  6147. {
  6148. //qDebug() << "REPLAY: " << quoteUnprintable(command);
  6149. Inputs inputs(command);
  6150. foreach (Input in, inputs) {
  6151. if (handleDefaultKey(in) != EventHandled)
  6152. break;
  6153. }
  6154. }
  6155. QString FakeVimHandler::Private::visualDotCommand() const
  6156. {
  6157. QTextCursor start(cursor());
  6158. QTextCursor end(start);
  6159. end.setPosition(end.anchor());
  6160. QString command;
  6161. if (isVisualCharMode())
  6162. command = _("v");
  6163. else if (isVisualLineMode())
  6164. command = _("V");
  6165. else if (isVisualBlockMode())
  6166. command = _("<c-v>");
  6167. else
  6168. return QString();
  6169. const int down = qAbs(start.blockNumber() - end.blockNumber());
  6170. if (down != 0)
  6171. command.append(QString::fromLatin1("%1j").arg(down));
  6172. const int right = start.positionInBlock() - end.positionInBlock();
  6173. if (right != 0) {
  6174. command.append(QString::number(qAbs(right)));
  6175. command.append(QLatin1Char(right < 0 && isVisualBlockMode() ? 'h' : 'l'));
  6176. }
  6177. return command;
  6178. }
  6179. void FakeVimHandler::Private::selectTextObject(bool simple, bool inner)
  6180. {
  6181. const int position1 = this->position();
  6182. const int anchor1 = this->anchor();
  6183. bool setupAnchor = (position1 == anchor1);
  6184. bool forward = anchor1 <= position1;
  6185. const int repeat = count();
  6186. // set anchor if not already set
  6187. if (setupAnchor) {
  6188. // Select nothing with 'inner' on empty line.
  6189. if (inner && atEmptyLine() && repeat == 1) {
  6190. m_movetype = MoveExclusive;
  6191. return;
  6192. }
  6193. moveToBoundaryStart(1, simple, false);
  6194. setAnchor();
  6195. } else if (forward) {
  6196. moveRight();
  6197. if (atEndOfLine())
  6198. moveRight();
  6199. } else {
  6200. moveLeft();
  6201. if (atBlockStart())
  6202. moveLeft();
  6203. }
  6204. if (inner) {
  6205. moveToBoundaryEnd(repeat, simple);
  6206. } else {
  6207. const int direction = forward ? 1 : -1;
  6208. for (int i = 0; i < repeat; ++i) {
  6209. // select leading spaces
  6210. bool leadingSpace = characterAtCursor().isSpace();
  6211. if (leadingSpace) {
  6212. if (forward)
  6213. moveToNextBoundaryStart(1, simple);
  6214. else
  6215. moveToNextBoundaryEnd(1, simple, false);
  6216. }
  6217. // select word
  6218. if (forward)
  6219. moveToWordEnd(1, simple);
  6220. else
  6221. moveToWordStart(1, simple, false);
  6222. // select trailing spaces if no leading space
  6223. if (!leadingSpace && document()->characterAt(position() + direction).isSpace()
  6224. && !atBlockStart()) {
  6225. if (forward)
  6226. moveToNextBoundaryEnd(1, simple);
  6227. else
  6228. moveToNextBoundaryStart(1, simple, false);
  6229. }
  6230. // if there are no trailing spaces in selection select all leading spaces
  6231. // after previous character
  6232. if (setupAnchor && (!characterAtCursor().isSpace() || atBlockEnd())) {
  6233. int min = block().position();
  6234. int pos = anchor();
  6235. while (pos >= min && document()->characterAt(--pos).isSpace()) {}
  6236. if (pos >= min)
  6237. setAnchorAndPosition(pos + 1, position());
  6238. }
  6239. if (i + 1 < repeat) {
  6240. if (forward) {
  6241. moveRight();
  6242. if (atEndOfLine())
  6243. moveRight();
  6244. } else {
  6245. moveLeft();
  6246. if (atBlockStart())
  6247. moveLeft();
  6248. }
  6249. }
  6250. }
  6251. }
  6252. if (inner) {
  6253. m_movetype = MoveInclusive;
  6254. } else {
  6255. m_movetype = MoveExclusive;
  6256. if (isNoVisualMode()) {
  6257. moveRight();
  6258. if (atEndOfLine())
  6259. moveRight();
  6260. } else if (isVisualLineMode()) {
  6261. m_visualMode = VisualCharMode;
  6262. }
  6263. }
  6264. setTargetColumn();
  6265. }
  6266. void FakeVimHandler::Private::selectWordTextObject(bool inner)
  6267. {
  6268. selectTextObject(false, inner);
  6269. }
  6270. void FakeVimHandler::Private::selectWORDTextObject(bool inner)
  6271. {
  6272. selectTextObject(true, inner);
  6273. }
  6274. void FakeVimHandler::Private::selectSentenceTextObject(bool inner)
  6275. {
  6276. Q_UNUSED(inner);
  6277. }
  6278. void FakeVimHandler::Private::selectParagraphTextObject(bool inner)
  6279. {
  6280. Q_UNUSED(inner);
  6281. }
  6282. bool FakeVimHandler::Private::selectBlockTextObject(bool inner,
  6283. char left, char right)
  6284. {
  6285. QString sleft = QString(QLatin1Char(left));
  6286. QString sright = QString(QLatin1Char(right));
  6287. int p1 = blockBoundary(sleft, sright, false, count());
  6288. if (p1 == -1)
  6289. return false;
  6290. int p2 = blockBoundary(sleft, sright, true, count());
  6291. if (p2 == -1)
  6292. return false;
  6293. if (inner)
  6294. p1 += sleft.size();
  6295. else
  6296. p2 -= sright.size() - 2;
  6297. if (isVisualMode())
  6298. --p2;
  6299. setAnchorAndPosition(p1, p2);
  6300. m_movetype = MoveExclusive;
  6301. return true;
  6302. }
  6303. bool FakeVimHandler::Private::changeNumberTextObject(int count)
  6304. {
  6305. const QTextBlock block = this->block();
  6306. const QString lineText = block.text();
  6307. const int posMin = cursor().positionInBlock() + 1;
  6308. // find first decimal, hexadecimal or octal number under or after cursor position
  6309. QRegExp re(_("(0[xX])(0*[0-9a-fA-F]+)|(0)(0*[0-7]+)(?=\\D|$)|(\\d+)"));
  6310. int pos = 0;
  6311. while ((pos = re.indexIn(lineText, pos)) != -1 && pos + re.matchedLength() < posMin)
  6312. ++pos;
  6313. if (pos == -1)
  6314. return false;
  6315. int len = re.matchedLength();
  6316. QString prefix = re.cap(1) + re.cap(3);
  6317. bool hex = prefix.length() >= 2 && (prefix[1].toLower() == QLatin1Char('x'));
  6318. bool octal = !hex && !prefix.isEmpty();
  6319. const QString num = hex ? re.cap(2) : octal ? re.cap(4) : re.cap(5);
  6320. // parse value
  6321. bool ok;
  6322. int base = hex ? 16 : octal ? 8 : 10;
  6323. qlonglong value; // decimal value
  6324. qlonglong uvalue; // hexadecimal or octal value (only unsigned)
  6325. if (hex || octal)
  6326. uvalue = num.toULongLong(&ok, base);
  6327. else
  6328. value = num.toLongLong(&ok, base);
  6329. QTC_ASSERT(ok, qDebug() << "Cannot parse number:" << num << "base:" << base; return false);
  6330. // negative decimal number
  6331. if (!octal && !hex && pos > 0 && lineText[pos - 1] == QLatin1Char('-')) {
  6332. value = -value;
  6333. --pos;
  6334. ++len;
  6335. }
  6336. // result to string
  6337. QString repl;
  6338. if (hex || octal)
  6339. repl = QString::number(uvalue + count, base);
  6340. else
  6341. repl = QString::number(value + count, base);
  6342. // convert hexadecimal number to upper-case if last letter was upper-case
  6343. if (hex) {
  6344. const int lastLetter = num.lastIndexOf(QRegExp(_("[a-fA-F]")));
  6345. if (lastLetter != -1 && num[lastLetter].isUpper())
  6346. repl = repl.toUpper();
  6347. }
  6348. // preserve leading zeroes
  6349. if ((octal || hex) && repl.size() < num.size())
  6350. prefix.append(QString::fromLatin1("0").repeated(num.size() - repl.size()));
  6351. repl.prepend(prefix);
  6352. pos += block.position();
  6353. setUndoPosition();
  6354. setAnchorAndPosition(pos, pos + len);
  6355. replaceText(currentRange(), repl);
  6356. setPosition(pos + repl.size() - 1);
  6357. return true;
  6358. }
  6359. bool FakeVimHandler::Private::selectQuotedStringTextObject(bool inner,
  6360. const QString &quote)
  6361. {
  6362. QTextCursor tc = cursor();
  6363. int sz = quote.size();
  6364. QTextCursor tc1;
  6365. QTextCursor tc2(document());
  6366. while (tc2 <= tc) {
  6367. tc1 = document()->find(quote, tc2);
  6368. if (tc1.isNull() || tc1.anchor() > tc.position())
  6369. return false;
  6370. tc2 = document()->find(quote, tc1);
  6371. if (tc2.isNull())
  6372. return false;
  6373. }
  6374. int p1 = tc1.position();
  6375. int p2 = tc2.position();
  6376. if (inner) {
  6377. p2 = qMax(p1, p2 - sz);
  6378. if (document()->characterAt(p1) == ParagraphSeparator)
  6379. ++p1;
  6380. } else {
  6381. p1 -= sz;
  6382. p2 -= sz - 1;
  6383. }
  6384. if (isVisualMode())
  6385. --p2;
  6386. setAnchorAndPosition(p1, p2);
  6387. m_movetype = MoveExclusive;
  6388. return true;
  6389. }
  6390. Mark FakeVimHandler::Private::mark(QChar code) const
  6391. {
  6392. if (isVisualMode()) {
  6393. if (code == QLatin1Char('<'))
  6394. return CursorPosition(document(), qMin(anchor(), position()));
  6395. if (code == QLatin1Char('>'))
  6396. return CursorPosition(document(), qMax(anchor(), position()));
  6397. }
  6398. if (code == QLatin1Char('.'))
  6399. return m_lastChangePosition;
  6400. if (code.isUpper())
  6401. return g.marks.value(code);
  6402. return m_marks.value(code);
  6403. }
  6404. void FakeVimHandler::Private::setMark(QChar code, CursorPosition position)
  6405. {
  6406. if (code.isUpper())
  6407. g.marks[code] = Mark(position, m_currentFileName);
  6408. else
  6409. m_marks[code] = Mark(position);
  6410. }
  6411. bool FakeVimHandler::Private::jumpToMark(QChar mark, bool backTickMode)
  6412. {
  6413. Mark m = this->mark(mark);
  6414. if (!m.isValid()) {
  6415. showMessage(MessageError, msgMarkNotSet(mark));
  6416. return false;
  6417. }
  6418. if (!m.isLocal(m_currentFileName)) {
  6419. emit q->jumpToGlobalMark(mark, backTickMode, m.fileName);
  6420. return false;
  6421. }
  6422. if (mark == QLatin1Char('\'') && !m_jumpListUndo.isEmpty())
  6423. m_jumpListUndo.pop();
  6424. recordJump();
  6425. setCursorPosition(m.position);
  6426. if (!backTickMode)
  6427. moveToFirstNonBlankOnLine();
  6428. if (m_submode == NoSubMode)
  6429. setAnchor();
  6430. setTargetColumn();
  6431. return true;
  6432. }
  6433. void FakeVimHandler::Private::updateMarks(const Marks &newMarks)
  6434. {
  6435. for (MarksIterator it(newMarks); it.hasNext(); ) {
  6436. it.next();
  6437. m_marks[it.key()] = it.value();
  6438. }
  6439. }
  6440. RangeMode FakeVimHandler::Private::registerRangeMode(int reg) const
  6441. {
  6442. bool isClipboard;
  6443. bool isSelection;
  6444. getRegisterType(reg, &isClipboard, &isSelection);
  6445. if (isClipboard || isSelection) {
  6446. QClipboard *clipboard = QApplication::clipboard();
  6447. QClipboard::Mode mode = isClipboard ? QClipboard::Clipboard : QClipboard::Selection;
  6448. // Use range mode from Vim's clipboard data if available.
  6449. const QMimeData *data = clipboard->mimeData(mode);
  6450. if (data != 0 && data->hasFormat(vimMimeText)) {
  6451. QByteArray bytes = data->data(vimMimeText);
  6452. if (bytes.length() > 0)
  6453. return static_cast<RangeMode>(bytes.at(0));
  6454. }
  6455. // If register content is clipboard:
  6456. // - return RangeLineMode if text ends with new line char,
  6457. // - return RangeCharMode otherwise.
  6458. QString text = clipboard->text(mode);
  6459. return (text.endsWith(QLatin1Char('\n')) || text.endsWith(QLatin1Char('\r'))) ? RangeLineMode : RangeCharMode;
  6460. }
  6461. return g.registers[reg].rangemode;
  6462. }
  6463. void FakeVimHandler::Private::setRegister(int reg, const QString &contents, RangeMode mode)
  6464. {
  6465. bool copyToClipboard;
  6466. bool copyToSelection;
  6467. getRegisterType(reg, &copyToClipboard, &copyToSelection);
  6468. QString contents2 = contents;
  6469. if (mode == RangeLineMode && !contents2.endsWith(QLatin1Char('\n')))
  6470. contents2.append(QLatin1Char('\n'));
  6471. if (copyToClipboard || copyToSelection) {
  6472. if (copyToClipboard)
  6473. setClipboardData(contents2, mode, QClipboard::Clipboard);
  6474. if (copyToSelection)
  6475. setClipboardData(contents2, mode, QClipboard::Selection);
  6476. } else {
  6477. g.registers[reg].contents = contents2;
  6478. g.registers[reg].rangemode = mode;
  6479. }
  6480. }
  6481. QString FakeVimHandler::Private::registerContents(int reg) const
  6482. {
  6483. bool copyFromClipboard;
  6484. bool copyFromSelection;
  6485. getRegisterType(reg, &copyFromClipboard, &copyFromSelection);
  6486. if (copyFromClipboard || copyFromSelection) {
  6487. QClipboard *clipboard = QApplication::clipboard();
  6488. if (copyFromClipboard)
  6489. return clipboard->text(QClipboard::Clipboard);
  6490. if (copyFromSelection)
  6491. return clipboard->text(QClipboard::Selection);
  6492. }
  6493. return g.registers[reg].contents;
  6494. }
  6495. void FakeVimHandler::Private::getRegisterType(int reg, bool *isClipboard, bool *isSelection) const
  6496. {
  6497. bool clipboard = false;
  6498. bool selection = false;
  6499. if (reg == QLatin1Char('"')) {
  6500. QStringList list = config(ConfigClipboard).toString().split(QLatin1Char(','));
  6501. clipboard = list.contains(_("unnamedplus"));
  6502. selection = list.contains(_("unnamed"));
  6503. } else if (reg == QLatin1Char('+')) {
  6504. clipboard = true;
  6505. } else if (reg == QLatin1Char('*')) {
  6506. selection = true;
  6507. }
  6508. // selection (primary) is clipboard on systems without selection support
  6509. if (selection && !QApplication::clipboard()->supportsSelection()) {
  6510. clipboard = true;
  6511. selection = false;
  6512. }
  6513. if (isClipboard != 0)
  6514. *isClipboard = clipboard;
  6515. if (isSelection != 0)
  6516. *isSelection = selection;
  6517. }
  6518. ///////////////////////////////////////////////////////////////////////
  6519. //
  6520. // FakeVimHandler
  6521. //
  6522. ///////////////////////////////////////////////////////////////////////
  6523. FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
  6524. : QObject(parent), d(new Private(this, widget))
  6525. {}
  6526. FakeVimHandler::~FakeVimHandler()
  6527. {
  6528. delete d;
  6529. }
  6530. // gracefully handle that the parent editor is deleted
  6531. void FakeVimHandler::disconnectFromEditor()
  6532. {
  6533. d->m_textedit = 0;
  6534. d->m_plaintextedit = 0;
  6535. }
  6536. bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
  6537. {
  6538. bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
  6539. // Catch mouse events on the viewport.
  6540. QWidget *viewport = 0;
  6541. if (d->m_plaintextedit)
  6542. viewport = d->m_plaintextedit->viewport();
  6543. else if (d->m_textedit)
  6544. viewport = d->m_textedit->viewport();
  6545. if (ob == viewport) {
  6546. if (active && ev->type() == QEvent::MouseButtonRelease) {
  6547. QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
  6548. if (mev->button() == Qt::LeftButton) {
  6549. d->importSelection();
  6550. //return true;
  6551. }
  6552. }
  6553. if (active && ev->type() == QEvent::MouseButtonPress) {
  6554. QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
  6555. if (mev->button() == Qt::LeftButton)
  6556. d->m_visualMode = NoVisualMode;
  6557. }
  6558. return QObject::eventFilter(ob, ev);
  6559. }
  6560. if (active && ev->type() == QEvent::Shortcut) {
  6561. d->passShortcuts(false);
  6562. return false;
  6563. }
  6564. if (active && ev->type() == QEvent::InputMethod && ob == d->editor()) {
  6565. // This handles simple dead keys. The sequence of events is
  6566. // KeyRelease-InputMethod-KeyRelease for dead keys instead of
  6567. // KeyPress-KeyRelease as for simple keys. As vi acts on key presses,
  6568. // we have to act on the InputMethod event.
  6569. // FIXME: A first approximation working for e.g. ^ on a German keyboard
  6570. QInputMethodEvent *imev = static_cast<QInputMethodEvent *>(ev);
  6571. KEY_DEBUG("INPUTMETHOD" << imev->commitString() << imev->preeditString());
  6572. QString commitString = imev->commitString();
  6573. int key = commitString.size() == 1 ? commitString.at(0).unicode() : 0;
  6574. QKeyEvent kev(QEvent::KeyPress, key, Qt::KeyboardModifiers(), commitString);
  6575. EventResult res = d->handleEvent(&kev);
  6576. return res == EventHandled || res == EventCancelled;
  6577. }
  6578. if (active && ev->type() == QEvent::KeyPress &&
  6579. (ob == d->editor() || (d->m_mode == ExMode || d->m_subsubmode == SearchSubSubMode))) {
  6580. QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
  6581. KEY_DEBUG("KEYPRESS" << kev->key() << kev->text() << QChar(kev->key()));
  6582. EventResult res = d->handleEvent(kev);
  6583. //if (d->m_mode == InsertMode)
  6584. // emit completionRequested();
  6585. // returning false core the app see it
  6586. //KEY_DEBUG("HANDLED CODE:" << res);
  6587. //return res != EventPassedToCore;
  6588. //return true;
  6589. return res == EventHandled || res == EventCancelled;
  6590. }
  6591. if (active && ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
  6592. QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
  6593. if (d->wantsOverride(kev)) {
  6594. KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
  6595. ev->accept(); // accepting means "don't run the shortcuts"
  6596. return true;
  6597. }
  6598. KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
  6599. return true;
  6600. }
  6601. if (active && ev->type() == QEvent::FocusIn && ob == d->editor())
  6602. d->focus();
  6603. return QObject::eventFilter(ob, ev);
  6604. }
  6605. void FakeVimHandler::installEventFilter()
  6606. {
  6607. d->installEventFilter();
  6608. }
  6609. void FakeVimHandler::setupWidget()
  6610. {
  6611. d->setupWidget();
  6612. }
  6613. void FakeVimHandler::restoreWidget(int tabSize)
  6614. {
  6615. d->restoreWidget(tabSize);
  6616. }
  6617. void FakeVimHandler::handleCommand(const QString &cmd)
  6618. {
  6619. d->enterFakeVim();
  6620. d->handleCommand(cmd);
  6621. d->leaveFakeVim();
  6622. }
  6623. void FakeVimHandler::handleReplay(const QString &keys)
  6624. {
  6625. d->enterFakeVim();
  6626. d->replay(keys);
  6627. d->leaveFakeVim();
  6628. }
  6629. void FakeVimHandler::handleInput(const QString &keys)
  6630. {
  6631. Inputs inputs(keys);
  6632. d->enterFakeVim();
  6633. foreach (const Input &input, inputs)
  6634. d->handleKey(input);
  6635. d->leaveFakeVim();
  6636. }
  6637. void FakeVimHandler::setCurrentFileName(const QString &fileName)
  6638. {
  6639. d->m_currentFileName = fileName;
  6640. }
  6641. QString FakeVimHandler::currentFileName() const
  6642. {
  6643. return d->m_currentFileName;
  6644. }
  6645. void FakeVimHandler::showMessage(MessageLevel level, const QString &msg)
  6646. {
  6647. d->showMessage(level, msg);
  6648. }
  6649. QWidget *FakeVimHandler::widget()
  6650. {
  6651. return d->editor();
  6652. }
  6653. // Test only
  6654. int FakeVimHandler::physicalIndentation(const QString &line) const
  6655. {
  6656. Column ind = d->indentation(line);
  6657. return ind.physical;
  6658. }
  6659. int FakeVimHandler::logicalIndentation(const QString &line) const
  6660. {
  6661. Column ind = d->indentation(line);
  6662. return ind.logical;
  6663. }
  6664. QString FakeVimHandler::tabExpand(int n) const
  6665. {
  6666. return d->tabExpand(n);
  6667. }
  6668. void FakeVimHandler::miniBufferTextEdited(const QString &text, int cursorPos, int anchorPos)
  6669. {
  6670. d->miniBufferTextEdited(text, cursorPos, anchorPos);
  6671. }
  6672. void FakeVimHandler::setTextCursorPosition(int position)
  6673. {
  6674. int pos = qMax(0, qMin(position, d->lastPositionInDocument()));
  6675. if (d->isVisualMode())
  6676. d->setPosition(pos);
  6677. else
  6678. d->setAnchorAndPosition(pos, pos);
  6679. d->setTargetColumn();
  6680. }
  6681. bool FakeVimHandler::jumpToLocalMark(QChar mark, bool backTickMode)
  6682. {
  6683. return d->jumpToMark(mark, backTickMode);
  6684. }
  6685. } // namespace Internal
  6686. } // namespace FakeVim
  6687. #include "fakevimhandler.moc"