PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/src/plugins/fakevim/fakevimhandler.cpp

https://bitbucket.org/kpozn/qt-creator-py-reborn
C++ | 5366 lines | 4912 code | 223 blank | 231 comment | 605 complexity | 78dac7466090e757c1cacbc672aaa7e1 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**************************************************************************
  2. **
  3. ** This file is part of Qt Creator
  4. **
  5. ** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
  6. **
  7. ** Contact: Nokia Corporation (qt-info@nokia.com)
  8. **
  9. **
  10. ** GNU Lesser General Public License Usage
  11. **
  12. ** This file may be used under the terms of the GNU Lesser General Public
  13. ** License version 2.1 as published by the Free Software Foundation and
  14. ** appearing in the file LICENSE.LGPL included in the packaging of this file.
  15. ** Please review the following information to ensure the GNU Lesser General
  16. ** Public License version 2.1 requirements will be met:
  17. ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  18. **
  19. ** In addition, as a special exception, Nokia gives you certain additional
  20. ** rights. These rights are described in the Nokia Qt LGPL Exception
  21. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  22. **
  23. ** Other Usage
  24. **
  25. ** Alternatively, this file may be used in accordance with the terms and
  26. ** conditions contained in a signed written agreement between you and Nokia.
  27. **
  28. ** If you have questions regarding the use of this file, please contact
  29. ** Nokia at qt-info@nokia.com.
  30. **
  31. **************************************************************************/
  32. //
  33. // ATTENTION:
  34. //
  35. // 1 Please do not add any direct dependencies to other Qt Creator code here.
  36. // Instead emit signals and let the FakeVimPlugin channel the information to
  37. // Qt Creator. The idea is to keep this file here in a "clean" state that
  38. // allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
  39. //
  40. // 2 There are a few auto tests located in ../../../tests/auto/fakevim.
  41. // Commands that are covered there are marked as "// tested" below.
  42. //
  43. // 3 Some conventions:
  44. //
  45. // Use 1 based line numbers and 0 based column numbers. Even though
  46. // the 1 based line are not nice it matches vim's and QTextEdit's 'line'
  47. // concepts.
  48. //
  49. // Do not pass QTextCursor etc around unless really needed. Convert
  50. // early to line/column.
  51. //
  52. // A QTextCursor is always between characters, whereas vi's cursor is always
  53. // over a character. FakeVim interprets the QTextCursor to be over the character
  54. // to the right of the QTextCursor's position().
  55. //
  56. // A current "region of interest"
  57. // spans between anchor(), (i.e. the character below anchor()), and
  58. // position(). The character below position() is not included
  59. // if the last movement command was exclusive (MoveExclusive).
  60. //
  61. #include "fakevimhandler.h"
  62. #include <utils/qtcassert.h>
  63. #include <QDebug>
  64. #include <QFile>
  65. #include <QObject>
  66. #include <QPointer>
  67. #include <QProcess>
  68. #include <QRegExp>
  69. #include <QTextStream>
  70. #include <QTimer>
  71. #include <QtAlgorithms>
  72. #include <QStack>
  73. #include <QApplication>
  74. #include <QInputMethodEvent>
  75. #include <QKeyEvent>
  76. #include <QLineEdit>
  77. #include <QPlainTextEdit>
  78. #include <QScrollBar>
  79. #include <QTextBlock>
  80. #include <QTextCursor>
  81. #include <QTextDocumentFragment>
  82. #include <QTextEdit>
  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() << << document()->availableUndoSteps() << 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 EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
  117. enum {
  118. #ifdef Q_OS_MAC
  119. RealControlModifier = Qt::MetaModifier
  120. #else
  121. RealControlModifier = Qt::ControlModifier
  122. #endif
  123. };
  124. // Enforce use of RealControlModifier by breaking the compilation.
  125. #define MetaModifier // Use RealControlModifier instead
  126. #define ControlModifier // Use RealControlModifier instead
  127. const int ParagraphSeparator = 0x00002029;
  128. typedef QLatin1String _;
  129. using namespace Qt;
  130. /*! A \e Mode represents one of the basic modes of operation of FakeVim.
  131. */
  132. enum Mode
  133. {
  134. InsertMode,
  135. ReplaceMode,
  136. CommandMode,
  137. ExMode
  138. };
  139. /*! A \e SubMode is used for things that require one more data item
  140. and are 'nested' behind a \l Mode.
  141. */
  142. enum SubMode
  143. {
  144. NoSubMode,
  145. ChangeSubMode, // Used for c
  146. DeleteSubMode, // Used for d
  147. FilterSubMode, // Used for !
  148. IndentSubMode, // Used for =
  149. RegisterSubMode, // Used for "
  150. ShiftLeftSubMode, // Used for <
  151. ShiftRightSubMode, // Used for >
  152. TransformSubMode, // Used for ~/gu/gU
  153. WindowSubMode, // Used for Ctrl-w
  154. YankSubMode, // Used for y
  155. ZSubMode, // Used for z
  156. CapitalZSubMode, // Used for Z
  157. ReplaceSubMode, // Used for r
  158. OpenSquareSubMode, // Used for [
  159. CloseSquareSubMode // Used for ]
  160. };
  161. /*! A \e SubSubMode is used for things that require one more data item
  162. and are 'nested' behind a \l SubMode.
  163. */
  164. enum SubSubMode
  165. {
  166. NoSubSubMode,
  167. FtSubSubMode, // Used for f, F, t, T.
  168. MarkSubSubMode, // Used for m.
  169. BackTickSubSubMode, // Used for `.
  170. TickSubSubMode, // Used for '.
  171. InvertCaseSubSubMode, // Used for ~.
  172. DownCaseSubSubMode, // Used for gu.
  173. UpCaseSubSubMode, // Used for gU.
  174. TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
  175. SearchSubSubMode
  176. };
  177. enum VisualMode
  178. {
  179. NoVisualMode,
  180. VisualCharMode,
  181. VisualLineMode,
  182. VisualBlockMode
  183. };
  184. enum MoveType
  185. {
  186. MoveExclusive,
  187. MoveInclusive,
  188. MoveLineWise
  189. };
  190. /*!
  191. \enum RangeMode
  192. The \e RangeMode serves as a means to define how the "Range" between
  193. the \l cursor and the \l anchor position is to be interpreted.
  194. \value RangeCharMode Entered by pressing \key v. The range includes
  195. all characters between cursor and anchor.
  196. \value RangeLineMode Entered by pressing \key V. The range includes
  197. all lines between the line of the cursor and
  198. the line of the anchor.
  199. \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
  200. newline when deleting.
  201. \value RangeBlockMode Entered by pressing \key Ctrl-v. The range includes
  202. all characters with line and column coordinates
  203. between line and columns coordinates of cursor and
  204. anchor.
  205. \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
  206. all characters in the affected lines up to the end
  207. of these lines.
  208. */
  209. enum EventResult
  210. {
  211. EventHandled,
  212. EventUnhandled,
  213. EventPassedToCore
  214. };
  215. struct Column
  216. {
  217. Column(int p, int l) : physical(p), logical(l) {}
  218. int physical; // Number of characters in the data.
  219. int logical; // Column on screen.
  220. };
  221. QDebug operator<<(QDebug ts, const Column &col)
  222. {
  223. return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
  224. }
  225. struct CursorPosition
  226. {
  227. // for jump history
  228. CursorPosition() : position(-1), scrollLine(-1) {}
  229. CursorPosition(int pos, int line) : position(pos), scrollLine(line) {}
  230. int position; // Position in document
  231. int scrollLine; // First visible line
  232. };
  233. struct Register
  234. {
  235. Register() : rangemode(RangeCharMode) {}
  236. Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
  237. Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
  238. QString contents;
  239. RangeMode rangemode;
  240. };
  241. QDebug operator<<(QDebug ts, const Register &reg)
  242. {
  243. return ts << reg.contents;
  244. }
  245. struct SearchData
  246. {
  247. SearchData()
  248. {
  249. forward = true;
  250. mustMove = true;
  251. highlightMatches = true;
  252. highlightCursor = true;
  253. }
  254. QString needle;
  255. bool forward;
  256. bool mustMove;
  257. bool highlightMatches;
  258. bool highlightCursor;
  259. };
  260. Range::Range()
  261. : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
  262. {}
  263. Range::Range(int b, int e, RangeMode m)
  264. : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
  265. {}
  266. QString Range::toString() const
  267. {
  268. return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
  269. .arg(rangemode);
  270. }
  271. QDebug operator<<(QDebug ts, const Range &range)
  272. {
  273. return ts << '[' << range.beginPos << ',' << range.endPos << ']';
  274. }
  275. ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
  276. : cmd(c), hasBang(false), args(a), range(r), count(1)
  277. {}
  278. bool ExCommand::matches(const QString &min, const QString &full) const
  279. {
  280. return cmd.startsWith(min) && full.startsWith(cmd);
  281. }
  282. void ExCommand::setContentsFromLine(const QString &line)
  283. {
  284. cmd = line.section(' ', 0, 0);
  285. args = line.mid(cmd.size() + 1).trimmed();
  286. while (cmd.startsWith(QLatin1Char(':')))
  287. cmd.remove(0, 1);
  288. hasBang = cmd.endsWith('!');
  289. if (hasBang)
  290. cmd.chop(1);
  291. }
  292. QDebug operator<<(QDebug ts, const ExCommand &cmd)
  293. {
  294. return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
  295. }
  296. QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
  297. {
  298. foreach (const QTextEdit::ExtraSelection &sel, sels)
  299. ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
  300. return ts;
  301. }
  302. QString quoteUnprintable(const QString &ba)
  303. {
  304. QString res;
  305. for (int i = 0, n = ba.size(); i != n; ++i) {
  306. const QChar c = ba.at(i);
  307. const int cc = c.unicode();
  308. if (c.isPrint())
  309. res += c;
  310. else if (cc == '\n')
  311. res += _("<CR>");
  312. else
  313. res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
  314. }
  315. return res;
  316. }
  317. static bool startsWithWhitespace(const QString &str, int col)
  318. {
  319. QTC_ASSERT(str.size() >= col, return false);
  320. for (int i = 0; i < col; ++i) {
  321. uint u = str.at(i).unicode();
  322. if (u != ' ' && u != '\t')
  323. return false;
  324. }
  325. return true;
  326. }
  327. inline QString msgMarkNotSet(const QString &text)
  328. {
  329. return FakeVimHandler::tr("Mark '%1' not set").arg(text);
  330. }
  331. class Input
  332. {
  333. public:
  334. // Remove some extra "information" on Mac.
  335. static int cleanModifier(int m) { return m & ~Qt::KeypadModifier; }
  336. Input()
  337. : m_key(0), m_xkey(0), m_modifiers(0) {}
  338. explicit Input(QChar x)
  339. : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
  340. Input(int k, int m, const QString &t)
  341. : m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
  342. {
  343. // On Mac, QKeyEvent::text() returns non-empty strings for
  344. // cursor keys. This breaks some of the logic later on
  345. // relying on text() being empty for "special" keys.
  346. // FIXME: Check the real conditions.
  347. if (m_text.size() == 1 && m_text.at(0).unicode() < ' ')
  348. m_text.clear();
  349. // m_xkey is only a cache.
  350. m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
  351. }
  352. bool isDigit() const
  353. {
  354. return m_xkey >= '0' && m_xkey <= '9';
  355. }
  356. bool isKey(int c) const
  357. {
  358. return !m_modifiers && m_key == c;
  359. }
  360. bool isBackspace() const
  361. {
  362. return m_key == Key_Backspace || isControl('h');
  363. }
  364. bool isReturn() const
  365. {
  366. return m_key == Key_Return || m_key == Key_Enter;
  367. }
  368. bool isEscape() const
  369. {
  370. return isKey(Key_Escape) || isKey(27) || isControl('c')
  371. || isControl(Key_BracketLeft);
  372. }
  373. bool is(int c) const
  374. {
  375. return m_xkey == c && m_modifiers != RealControlModifier;
  376. }
  377. bool isControl(int c) const
  378. {
  379. return m_modifiers == RealControlModifier
  380. && (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
  381. }
  382. bool isShift(int c) const
  383. {
  384. return m_modifiers == Qt::ShiftModifier && m_xkey == c;
  385. }
  386. bool operator==(const Input &a) const
  387. {
  388. return a.m_key == m_key && a.m_modifiers == m_modifiers
  389. && m_text == a.m_text;
  390. }
  391. // Ignore e.g. ShiftModifier, which is not available in sourced data.
  392. bool matchesForMap(const Input &a) const
  393. {
  394. return (a.m_key == m_key || a.m_xkey == m_xkey) && m_text == a.m_text;
  395. }
  396. bool operator!=(const Input &a) const { return !operator==(a); }
  397. QString text() const { return m_text; }
  398. QChar asChar() const
  399. {
  400. return (m_text.size() == 1 ? m_text.at(0) : QChar());
  401. }
  402. int key() const { return m_key; }
  403. QChar raw() const
  404. {
  405. if (m_key == Key_Tab)
  406. return '\t';
  407. if (m_key == Key_Return)
  408. return '\n';
  409. return m_key;
  410. }
  411. QDebug dump(QDebug ts) const
  412. {
  413. return ts << m_key << '-' << m_modifiers << '-'
  414. << quoteUnprintable(m_text);
  415. }
  416. private:
  417. int m_key;
  418. int m_xkey;
  419. int m_modifiers;
  420. QString m_text;
  421. };
  422. QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }
  423. class Inputs : public QVector<Input>
  424. {
  425. public:
  426. Inputs() {}
  427. explicit Inputs(const QString &str) { parseFrom(str); }
  428. void parseFrom(const QString &str);
  429. };
  430. static bool iss(char a, char b)
  431. {
  432. if (a >= 'a')
  433. a -= 'a' - 'A';
  434. if (b >= 'a')
  435. b -= 'a' - 'A';
  436. return a == b;
  437. }
  438. void Inputs::parseFrom(const QString &str)
  439. {
  440. const int n = str.size();
  441. for (int i = 0; i < n; ++i) {
  442. uint c0 = str.at(i).unicode(), c1 = 0, c2 = 0, c3 = 0, c4 = 0;
  443. if (i + 1 < n)
  444. c1 = str.at(i + 1).unicode();
  445. if (i + 2 < n)
  446. c2 = str.at(i + 2).unicode();
  447. if (i + 3 < n)
  448. c3 = str.at(i + 3).unicode();
  449. if (i + 4 < n)
  450. c4 = str.at(i + 4).unicode();
  451. if (c0 == '<') {
  452. if (iss(c1, 'C') && c2 == '-' && c4 == '>') {
  453. uint c = (c3 < 90 ? c3 : c3 - 32);
  454. append(Input(c, RealControlModifier, QString(QChar(c - 64))));
  455. i += 4;
  456. } else if (iss(c1, 'C') && iss(c2, 'R') && c3 == '>') {
  457. append(Input(Key_Return, Qt::NoModifier, QString(QChar(13))));
  458. i += 3;
  459. } else if (iss(c1, 'E') && iss(c2, 'S') && iss(c3, 'C') && c4 == '>') {
  460. append(Input(Key_Escape, Qt::NoModifier, QString(QChar(27))));
  461. i += 4;
  462. } else {
  463. append(Input(QLatin1Char(c0)));
  464. }
  465. } else {
  466. append(Input(QLatin1Char(c0)));
  467. }
  468. }
  469. }
  470. // This wraps a string and a "cursor position".
  471. class CommandBuffer
  472. {
  473. public:
  474. CommandBuffer() : m_pos(0) {}
  475. void clear() { m_buffer.clear(); m_pos = 0; }
  476. void setContents(const QString &s) { m_buffer = s; m_pos = s.size(); }
  477. QString contents() const { return m_buffer; }
  478. bool isEmpty() const { return m_buffer.isEmpty(); }
  479. int cursorPos() const { return m_pos; }
  480. void insertChar(QChar c) { m_buffer.insert(m_pos++, c); }
  481. void insertText(const QString &s) { m_buffer.insert(m_pos, s); m_pos += s.size(); }
  482. void deleteChar() { if (m_pos) m_buffer.remove(--m_pos, 1); }
  483. void moveLeft() { if (m_pos) --m_pos; }
  484. void moveRight() { if (m_pos < m_buffer.size()) ++m_pos; }
  485. void moveStart() { m_pos = 0; }
  486. void moveEnd() { m_pos = m_buffer.size(); }
  487. QString display() const
  488. {
  489. QString msg;
  490. for (int i = 0; i != m_buffer.size(); ++i) {
  491. const QChar c = m_buffer.at(i);
  492. if (c.unicode() < 32) {
  493. msg += '^';
  494. msg += QChar(c.unicode() + 64);
  495. } else {
  496. msg += c;
  497. }
  498. }
  499. return msg;
  500. }
  501. bool handleInput(const Input &input)
  502. {
  503. if (input.isKey(Key_Left)) {
  504. moveLeft();
  505. } else if (input.isKey(Key_Right)) {
  506. moveRight();
  507. } else if (input.isKey(Key_Home)) {
  508. moveStart();
  509. } else if (input.isKey(Key_End)) {
  510. moveEnd();
  511. } else if (input.isKey(Key_Delete)) {
  512. if (m_pos < m_buffer.size())
  513. m_buffer.remove(m_pos, 1);
  514. } else if (!input.text().isEmpty()) {
  515. insertText(input.text());
  516. } else {
  517. return false;
  518. }
  519. return true;
  520. }
  521. private:
  522. QString m_buffer;
  523. int m_pos;
  524. };
  525. class History
  526. {
  527. public:
  528. History() : m_index(0) {}
  529. void append(const QString &item);
  530. void down() { m_index = qMin(m_index + 1, m_items.size()); }
  531. void up() { m_index = qMax(m_index - 1, 0); }
  532. void restart() { m_index = m_items.size(); }
  533. QString current() const { return m_items.value(m_index, QString()); }
  534. QStringList items() const { return m_items; }
  535. private:
  536. QStringList m_items;
  537. int m_index;
  538. };
  539. void History::append(const QString &item)
  540. {
  541. if (item.isEmpty())
  542. return;
  543. m_items.removeAll(item);
  544. m_items.append(item); m_index = m_items.size() - 1;
  545. }
  546. // Mappings for a specific mode.
  547. class ModeMapping : public QList<QPair<Inputs, Inputs> >
  548. {
  549. public:
  550. ModeMapping() { test(); }
  551. void test()
  552. {
  553. //insert(Inputs() << Input('A') << Input('A'),
  554. // Inputs() << Input('x') << Input('x'));
  555. }
  556. void insert(const Inputs &from, const Inputs &to)
  557. {
  558. for (int i = 0; i != size(); ++i)
  559. if (at(i).first == from) {
  560. (*this)[i].second = to;
  561. return;
  562. }
  563. append(QPair<Inputs, Inputs>(from, to));
  564. }
  565. void remove(const Inputs &from)
  566. {
  567. for (int i = 0; i != size(); ++i)
  568. if (at(i).first == from) {
  569. removeAt(i);
  570. return;
  571. }
  572. }
  573. // Returns 'false' if more input is needed to decide whether a mapping
  574. // needs to be applied. If a decision can be made, return 'true',
  575. // and replace *input with the mapped data.
  576. bool mappingDone(Inputs *inputs) const
  577. {
  578. // FIXME: inefficient.
  579. for (int i = 0; i != size(); ++i) {
  580. const Inputs &haystack = at(i).first;
  581. // A mapping
  582. if (couldTriggerMap(haystack, *inputs)) {
  583. if (haystack.size() != inputs->size())
  584. return false; // This can be extended.
  585. // Actual mapping.
  586. *inputs = at(i).second;
  587. return true;
  588. }
  589. }
  590. // No extensible mapping found. Use inputs as-is.
  591. return true;
  592. }
  593. private:
  594. static bool couldTriggerMap(const Inputs &haystack, const Inputs &needle)
  595. {
  596. // Input is already too long.
  597. if (needle.size() > haystack.size())
  598. return false;
  599. for (int i = 0; i != needle.size(); ++i) {
  600. if (!needle.at(i).matchesForMap(haystack.at(i)))
  601. return false;
  602. }
  603. return true;
  604. }
  605. };
  606. class FakeVimHandler::Private : public QObject
  607. {
  608. Q_OBJECT
  609. public:
  610. Private(FakeVimHandler *parent, QWidget *widget);
  611. EventResult handleEvent(QKeyEvent *ev);
  612. bool wantsOverride(QKeyEvent *ev);
  613. void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
  614. void handleExCommand(const QString &cmd);
  615. void installEventFilter();
  616. void passShortcuts(bool enable);
  617. void setupWidget();
  618. void restoreWidget(int tabSize);
  619. friend class FakeVimHandler;
  620. void init();
  621. EventResult handleKey(const Input &);
  622. Q_SLOT EventResult handleKey2();
  623. EventResult handleInsertMode(const Input &);
  624. EventResult handleReplaceMode(const Input &);
  625. EventResult handleCommandMode(const Input &);
  626. EventResult handleCommandMode1(const Input &);
  627. EventResult handleCommandMode2(const Input &);
  628. EventResult handleRegisterMode(const Input &);
  629. EventResult handleExMode(const Input &);
  630. EventResult handleOpenSquareSubMode(const Input &);
  631. EventResult handleCloseSquareSubMode(const Input &);
  632. EventResult handleSearchSubSubMode(const Input &);
  633. EventResult handleCommandSubSubMode(const Input &);
  634. void finishMovement(const QString &dotCommand = QString());
  635. void finishMovement(const QString &dotCommand, int count);
  636. void resetCommandMode();
  637. void search(const SearchData &sd);
  638. void searchBalanced(bool forward, QChar needle, QChar other);
  639. void highlightMatches(const QString &needle);
  640. void stopIncrementalFind();
  641. int mvCount() const { return m_mvcount.isEmpty() ? 1 : m_mvcount.toInt(); }
  642. int opCount() const { return m_opcount.isEmpty() ? 1 : m_opcount.toInt(); }
  643. int count() const { return mvCount() * opCount(); }
  644. QTextBlock block() const { return cursor().block(); }
  645. int leftDist() const { return position() - block().position(); }
  646. int rightDist() const { return block().length() - leftDist() - 1; }
  647. bool atBlockEnd() const { return cursor().atBlockEnd(); }
  648. bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
  649. int lastPositionInDocument() const; // Returns last valid position in doc.
  650. int firstPositionInLine(int line) const; // 1 based line, 0 based pos
  651. int lastPositionInLine(int line) const; // 1 based line, 0 based pos
  652. int lineForPosition(int pos) const; // 1 based line, 0 based pos
  653. QString lineContents(int line) const; // 1 based line
  654. void setLineContents(int line, const QString &contents); // 1 based line
  655. int linesOnScreen() const;
  656. int columnsOnScreen() const;
  657. int linesInDocument() const;
  658. // The following use all zero-based counting.
  659. int cursorLineOnScreen() const;
  660. int cursorLine() const;
  661. int physicalCursorColumn() const; // as stored in the data
  662. int logicalCursorColumn() const; // as visible on screen
  663. int physicalToLogicalColumn(int physical, const QString &text) const;
  664. int logicalToPhysicalColumn(int logical, const QString &text) const;
  665. Column cursorColumn() const; // as visible on screen
  666. int firstVisibleLine() const;
  667. void scrollToLine(int line);
  668. void scrollUp(int count);
  669. void scrollDown(int count) { scrollUp(-count); }
  670. CursorPosition cursorPosition() const
  671. { return CursorPosition(position(), firstVisibleLine()); }
  672. void setCursorPosition(const CursorPosition &p)
  673. { setPosition(p.position); scrollToLine(p.scrollLine); }
  674. // Helper functions for indenting/
  675. bool isElectricCharacter(QChar c) const;
  676. void indentSelectedText(QChar lastTyped = QChar());
  677. void indentText(const Range &range, QChar lastTyped = QChar());
  678. void shiftRegionLeft(int repeat = 1);
  679. void shiftRegionRight(int repeat = 1);
  680. void moveToFirstNonBlankOnLine();
  681. void moveToTargetColumn();
  682. void setTargetColumn() {
  683. m_targetColumn = logicalCursorColumn();
  684. m_visualTargetColumn = m_targetColumn;
  685. //qDebug() << "TARGET: " << m_targetColumn;
  686. }
  687. void moveToNextWord(bool simple, bool deleteWord = false);
  688. void moveToMatchingParanthesis();
  689. void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
  690. // Convenience wrappers to reduce line noise.
  691. void moveToStartOfLine();
  692. void moveToEndOfLine();
  693. void moveBehindEndOfLine();
  694. void moveUp(int n = 1) { moveDown(-n); }
  695. void moveDown(int n = 1);
  696. void dump(const char *msg) const {
  697. qDebug() << msg << "POS: " << anchor() << position()
  698. << "EXT: " << m_oldExternalAnchor << m_oldExternalPosition
  699. << "INT: " << m_oldInternalAnchor << m_oldInternalPosition
  700. << "VISUAL: " << m_visualMode;
  701. }
  702. void moveRight(int n = 1) {
  703. //dump("RIGHT 1");
  704. QTextCursor tc = cursor();
  705. tc.movePosition(Right, KeepAnchor, n);
  706. setCursor(tc);
  707. //dump("RIGHT 2");
  708. }
  709. void moveLeft(int n = 1) {
  710. QTextCursor tc = cursor();
  711. tc.movePosition(Left, KeepAnchor, n);
  712. setCursor(tc);
  713. }
  714. void setAnchor() {
  715. QTextCursor tc = cursor();
  716. tc.setPosition(tc.position(), MoveAnchor);
  717. setCursor(tc);
  718. }
  719. void setAnchor(int position) {
  720. QTextCursor tc = cursor();
  721. tc.setPosition(tc.anchor(), MoveAnchor);
  722. tc.setPosition(position, KeepAnchor);
  723. setCursor(tc);
  724. }
  725. void setPosition(int position) {
  726. QTextCursor tc = cursor();
  727. tc.setPosition(position, KeepAnchor);
  728. setCursor(tc);
  729. }
  730. void setAnchorAndPosition(int anchor, int position) {
  731. QTextCursor tc = cursor();
  732. tc.setPosition(anchor, MoveAnchor);
  733. tc.setPosition(position, KeepAnchor);
  734. setCursor(tc);
  735. }
  736. QTextCursor cursor() const { return EDITOR(textCursor()); }
  737. void setCursor(const QTextCursor &tc) { EDITOR(setTextCursor(tc)); }
  738. bool handleFfTt(QString key);
  739. // Helper function for handleExCommand returning 1 based line index.
  740. int readLineCode(QString &cmd);
  741. void enterInsertMode();
  742. void enterReplaceMode();
  743. void enterCommandMode();
  744. void enterExMode();
  745. void showRedMessage(const QString &msg);
  746. void showBlackMessage(const QString &msg);
  747. void notImplementedYet();
  748. void updateMiniBuffer();
  749. void updateSelection();
  750. void updateCursorShape();
  751. QWidget *editor() const;
  752. QTextDocument *document() const { return EDITOR(document()); }
  753. QChar characterAtCursor() const
  754. { return document()->characterAt(position()); }
  755. void beginEditBlock()
  756. { UNDO_DEBUG("BEGIN EDIT BLOCK"); cursor().beginEditBlock(); }
  757. void endEditBlock()
  758. { UNDO_DEBUG("END EDIT BLOCK"); cursor().endEditBlock(); }
  759. void joinPreviousEditBlock()
  760. { UNDO_DEBUG("JOIN"); cursor().joinPreviousEditBlock(); }
  761. void breakEditBlock() {
  762. QTextCursor tc = cursor();
  763. tc.clearSelection();
  764. tc.beginEditBlock();
  765. tc.insertText("x");
  766. tc.deletePreviousChar();
  767. tc.endEditBlock();
  768. setCursor(tc);
  769. }
  770. bool isVisualMode() const { return m_visualMode != NoVisualMode; }
  771. bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
  772. bool isVisualCharMode() const { return m_visualMode == VisualCharMode; }
  773. bool isVisualLineMode() const { return m_visualMode == VisualLineMode; }
  774. bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; }
  775. void updateEditor();
  776. void selectWordTextObject(bool inner);
  777. void selectWORDTextObject(bool inner);
  778. void selectSentenceTextObject(bool inner);
  779. void selectParagraphTextObject(bool inner);
  780. void selectBlockTextObject(bool inner, char left, char right);
  781. void changeNumberTextObject(bool doIncrement);
  782. void selectQuotedStringTextObject(bool inner, int type);
  783. Q_SLOT void importSelection();
  784. void exportSelection();
  785. void insertInInsertMode(const QString &text);
  786. public:
  787. QTextEdit *m_textedit;
  788. QPlainTextEdit *m_plaintextedit;
  789. bool m_wasReadOnly; // saves read-only state of document
  790. FakeVimHandler *q;
  791. Mode m_mode;
  792. bool m_passing; // let the core see the next event
  793. bool m_firstKeyPending;
  794. SubMode m_submode;
  795. SubSubMode m_subsubmode;
  796. Input m_subsubdata;
  797. int m_oldExternalPosition; // copy from last event to check for external changes
  798. int m_oldExternalAnchor;
  799. int m_oldInternalPosition; // copy from last event to check for external changes
  800. int m_oldInternalAnchor;
  801. int m_oldPosition; // FIXME: Merge with above.
  802. int m_register;
  803. QString m_mvcount;
  804. QString m_opcount;
  805. MoveType m_movetype;
  806. RangeMode m_rangemode;
  807. int m_visualInsertCount;
  808. bool m_fakeEnd;
  809. bool m_anchorPastEnd;
  810. bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
  811. int m_gflag; // whether current command started with 'g'
  812. QString m_commandPrefix;
  813. CommandBuffer m_commandBuffer;
  814. QString m_currentFileName;
  815. QString m_currentMessage;
  816. bool m_lastSearchForward;
  817. bool m_findPending;
  818. int m_findStartPosition;
  819. QString m_lastInsertion;
  820. QString m_lastDeletion;
  821. int anchor() const { return cursor().anchor(); }
  822. int position() const { return cursor().position(); }
  823. struct TransformationData
  824. {
  825. TransformationData(const QString &s, const QVariant &d)
  826. : from(s), extraData(d) {}
  827. QString from;
  828. QString to;
  829. QVariant extraData;
  830. };
  831. typedef void (Private::*Transformation)(TransformationData *td);
  832. void transformText(const Range &range, Transformation transformation,
  833. const QVariant &extraData = QVariant());
  834. void insertText(const Register &reg);
  835. void removeText(const Range &range);
  836. void removeTransform(TransformationData *td);
  837. void invertCase(const Range &range);
  838. void invertCaseTransform(TransformationData *td);
  839. void upCase(const Range &range);
  840. void upCaseTransform(TransformationData *td);
  841. void downCase(const Range &range);
  842. void downCaseTransform(TransformationData *td);
  843. void replaceText(const Range &range, const QString &str);
  844. void replaceByStringTransform(TransformationData *td);
  845. void replaceByCharTransform(TransformationData *td);
  846. QString selectText(const Range &range) const;
  847. void setCurrentRange(const Range &range);
  848. Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
  849. Range rangeFromCurrentLine() const;
  850. void yankText(const Range &range, int toregister = '"');
  851. void pasteText(bool afterCursor);
  852. // undo handling
  853. void undo();
  854. void redo();
  855. void setUndoPosition();
  856. QMap<int, int> m_undoCursorPosition; // revision -> position
  857. // extra data for '.'
  858. void replay(const QString &text, int count);
  859. void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
  860. void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
  861. // extra data for ';'
  862. QString m_semicolonCount;
  863. Input m_semicolonType; // 'f', 'F', 't', 'T'
  864. QString m_semicolonKey;
  865. // visual modes
  866. void toggleVisualMode(VisualMode visualMode);
  867. void leaveVisualMode();
  868. VisualMode m_visualMode;
  869. VisualMode m_oldVisualMode;
  870. // marks as lines
  871. int mark(int code) const;
  872. void setMark(int code, int position);
  873. typedef QHash<int, QTextCursor> Marks;
  874. typedef QHashIterator<int, QTextCursor> MarksIterator;
  875. Marks m_marks;
  876. // vi style configuration
  877. QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
  878. bool hasConfig(int code) const { return config(code).toBool(); }
  879. bool hasConfig(int code, const char *value) const // FIXME
  880. { return config(code).toString().contains(value); }
  881. int m_targetColumn; // -1 if past end of line
  882. int m_visualTargetColumn; // 'l' can move past eol in visual mode only
  883. // auto-indent
  884. QString tabExpand(int len) const;
  885. Column indentation(const QString &line) const;
  886. void insertAutomaticIndentation(bool goingDown);
  887. bool removeAutomaticIndentation(); // true if something removed
  888. // number of autoindented characters
  889. int m_justAutoIndented;
  890. void handleStartOfLine();
  891. // register handling
  892. QString registerContents(int reg) const;
  893. void setRegisterContents(int reg, const QString &contents);
  894. RangeMode registerRangeMode(int reg) const;
  895. void setRegisterRangeMode(int reg, RangeMode mode);
  896. void recordJump();
  897. QVector<CursorPosition> m_jumpListUndo;
  898. QVector<CursorPosition> m_jumpListRedo;
  899. int m_lastChangePosition;
  900. QList<QTextEdit::ExtraSelection> m_searchSelections;
  901. QTextCursor m_searchCursor;
  902. QString m_oldNeedle;
  903. QString m_lastSubstituteFlags;
  904. QRegExp m_lastSubstitutePattern;
  905. QString m_lastSubstituteReplacement;
  906. bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
  907. bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
  908. bool handleExBangCommand(const ExCommand &cmd);
  909. bool handleExDeleteCommand(const ExCommand &cmd);
  910. bool handleExGotoCommand(const ExCommand &cmd);
  911. bool handleExHistoryCommand(const ExCommand &cmd);
  912. bool handleExRegisterCommand(const ExCommand &cmd);
  913. bool handleExMapCommand(const ExCommand &cmd);
  914. bool handleExNohlsearchCommand(const ExCommand &cmd);
  915. bool handleExNormalCommand(const ExCommand &cmd);
  916. bool handleExReadCommand(const ExCommand &cmd);
  917. bool handleExRedoCommand(const ExCommand &cmd);
  918. bool handleExSetCommand(const ExCommand &cmd);
  919. bool handleExShiftCommand(const ExCommand &cmd);
  920. bool handleExSourceCommand(const ExCommand &cmd);
  921. bool handleExSubstituteCommand(const ExCommand &cmd);
  922. bool handleExWriteCommand(const ExCommand &cmd);
  923. bool handleExEchoCommand(const ExCommand &cmd);
  924. void timerEvent(QTimerEvent *ev);
  925. void setupCharClass();
  926. int charClass(QChar c, bool simple) const;
  927. signed char m_charClass[256];
  928. bool m_ctrlVActive;
  929. static struct GlobalData
  930. {
  931. GlobalData()
  932. {
  933. inputTimer = -1;
  934. }
  935. // Input.
  936. Inputs pendingInput;
  937. int inputTimer;
  938. // Repetition.
  939. QString dotCommand;
  940. // History for searches.
  941. History searchHistory;
  942. // History for :ex commands.
  943. History commandHistory;
  944. QHash<int, Register> registers;
  945. // All mappings.
  946. typedef QHash<char, ModeMapping> Mappings;
  947. Mappings mappings;
  948. } g;
  949. };
  950. FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
  951. FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
  952. {
  953. //static PythonHighlighterRules pythonRules;
  954. q = parent;
  955. m_textedit = qobject_cast<QTextEdit *>(widget);
  956. m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
  957. //new Highlighter(document(), &pythonRules);
  958. init();
  959. }
  960. void FakeVimHandler::Private::init()
  961. {
  962. m_mode = CommandMode;
  963. m_submode = NoSubMode;
  964. m_subsubmode = NoSubSubMode;
  965. m_passing = false;
  966. m_firstKeyPending = false;
  967. m_findPending = false;
  968. m_findStartPosition = -1;
  969. m_fakeEnd = false;
  970. m_positionPastEnd = false;
  971. m_anchorPastEnd = false;
  972. m_lastSearchForward = true;
  973. m_register = '"';
  974. m_gflag = false;
  975. m_visualMode = NoVisualMode;
  976. m_oldVisualMode = NoVisualMode;
  977. m_targetColumn = 0;
  978. m_visualTargetColumn = 0;
  979. m_movetype = MoveInclusive;
  980. m_justAutoIndented = 0;
  981. m_rangemode = RangeCharMode;
  982. m_ctrlVActive = false;
  983. m_oldInternalAnchor = -1;
  984. m_oldInternalPosition = -1;
  985. m_oldExternalAnchor = -1;
  986. m_oldExternalPosition = -1;
  987. m_oldPosition = -1;
  988. m_lastChangePosition = -1;
  989. setupCharClass();
  990. }
  991. bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
  992. {
  993. const int key = ev->key();
  994. const int mods = ev->modifiers();
  995. KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing);
  996. if (key == Key_Escape) {
  997. if (m_subsubmode == SearchSubSubMode)
  998. return true;
  999. // Not sure this feels good. People often hit Esc several times.
  1000. if (isNoVisualMode()
  1001. && m_mode == CommandMode
  1002. && m_submode == NoSubMode
  1003. && m_opcount.isEmpty()
  1004. && m_mvcount.isEmpty())
  1005. return false;
  1006. return true;
  1007. }
  1008. // We are interested in overriding most Ctrl key combinations.
  1009. if (mods == RealControlModifier
  1010. && !config(ConfigPassControlKey).toBool()
  1011. && ((key >= Key_A && key <= Key_Z && key != Key_K)
  1012. || key == Key_BracketLeft || key == Key_BracketRight)) {
  1013. // Ctrl-K is special as it is the Core's default notion of Locator
  1014. if (m_passing) {
  1015. KEY_DEBUG(" PASSING CTRL KEY");
  1016. // We get called twice on the same key
  1017. //m_passing = false;
  1018. return false;
  1019. }
  1020. KEY_DEBUG(" NOT PASSING CTRL KEY");
  1021. //updateMiniBuffer();
  1022. return true;
  1023. }
  1024. // Let other shortcuts trigger.
  1025. return false;
  1026. }
  1027. EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
  1028. {
  1029. const int key = ev->key();
  1030. const int mods = ev->modifiers();
  1031. if (key == Key_Shift || key == Key_Alt || key == Key_Control
  1032. || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
  1033. {
  1034. KEY_DEBUG("PLAIN MODIFIER");
  1035. return EventUnhandled;
  1036. }
  1037. if (m_passing) {
  1038. passShortcuts(false);
  1039. KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
  1040. //if (input.is(',')) { // use ',,' to leave, too.
  1041. // qDebug() << "FINISHED...";
  1042. // return EventHandled;
  1043. //}
  1044. m_passing = false;
  1045. updateMiniBuffer();
  1046. KEY_DEBUG(" PASS TO CORE");
  1047. return EventPassedToCore;
  1048. }
  1049. bool inSnippetMode = false;
  1050. QMetaObject::invokeMethod(editor(),
  1051. "inSnippetMode", Q_ARG(bool *, &inSnippetMode));
  1052. if (inSnippetMode)
  1053. return EventPassedToCore;
  1054. // Fake "End of line"
  1055. //m_tc = cursor();
  1056. //bool hasBlock = false;
  1057. //emit q->requestHasBlockSelection(&hasBlock);
  1058. //qDebug() << "IMPORT BLOCK 2:" << hasBlock;
  1059. //if (0 && hasBlock) {
  1060. // (pos > anc) ? --pos : --anc;
  1061. importSelection();
  1062. // Position changed externally, e.g. by code completion.
  1063. if (position() != m_oldPosition) {
  1064. setTargetColumn();
  1065. //qDebug() << "POSITION CHANGED EXTERNALLY";
  1066. if (m_mode == InsertMode) {
  1067. int dist = position() - m_oldPosition;
  1068. // Try to compensate for code completion
  1069. if (dist > 0 && dist <= physicalCursorColumn()) {
  1070. Range range(m_oldPosition, position());
  1071. m_lastInsertion.append(selectText(range));
  1072. }
  1073. } else if (!isVisualMode()) {
  1074. if (atEndOfLine())
  1075. moveLeft();
  1076. }
  1077. }
  1078. QTextCursor tc = cursor();
  1079. tc.setVisualNavigation(true);
  1080. setCursor(tc);
  1081. if (m_firstKeyPending) {
  1082. m_firstKeyPending = false;
  1083. recordJump();
  1084. }
  1085. if (m_fakeEnd)
  1086. moveRight();
  1087. //if ((mods & RealControlModifier) != 0) {
  1088. // if (key >= Key_A && key <= Key_Z)
  1089. // key = shift(key); // make it lower case
  1090. // key = control(key);
  1091. //} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
  1092. // key = shift(key);
  1093. //}
  1094. //QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
  1095. // || !atBlockEnd() || block().length() <= 1,
  1096. // qDebug() << "Cursor at EOL before key handler");
  1097. EventResult result = handleKey(Input(key, mods, ev->text()));
  1098. // The command might have destroyed the editor.
  1099. if (m_textedit || m_plaintextedit) {
  1100. // We fake vi-style end-of-line behaviour
  1101. m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
  1102. //QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
  1103. // || !atBlockEnd() || block().length() <= 1,
  1104. // qDebug() << "Cursor at EOL after key handler");
  1105. if (m_fakeEnd)
  1106. moveLeft();
  1107. m_oldPosition = position();
  1108. if (hasConfig(ConfigShowMarks))
  1109. updateSelection();
  1110. exportSelection();
  1111. updateCursorShape();
  1112. }
  1113. return result;
  1114. }
  1115. void FakeVimHandler::Private::installEventFilter()
  1116. {
  1117. EDITOR(viewport()->installEventFilter(q));
  1118. EDITOR(installEventFilter(q));
  1119. }
  1120. void FakeVimHandler::Private::setupWidget()
  1121. {
  1122. enterCommandMode();
  1123. if (m_textedit) {
  1124. m_textedit->setLineWrapMode(QTextEdit::NoWrap);
  1125. } else if (m_plaintextedit) {
  1126. m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
  1127. }
  1128. m_wasReadOnly = EDITOR(isReadOnly());
  1129. m_firstKeyPending = true;
  1130. updateEditor();
  1131. importSelection();
  1132. updateMiniBuffer();
  1133. updateCursorShape();
  1134. }
  1135. void FakeVimHandler::Private::exportSelection()
  1136. {
  1137. int pos = position();
  1138. int anc = anchor();
  1139. m_oldInternalPosition = pos;
  1140. m_oldInternalAnchor = anc;
  1141. if (isVisualMode()) {
  1142. if (pos >= anc)
  1143. setAnchorAndPosition(anc, pos + 1);
  1144. else
  1145. setAnchorAndPosition(anc + 1, pos);
  1146. if (m_visualMode == VisualBlockMode) {
  1147. emit q->requestSetBlockSelection(false);
  1148. emit q->requestSetBlockSelection(true);
  1149. } else if (m_visualMode == VisualLineMode) {
  1150. const int posLine = lineForPosition(pos);
  1151. const int ancLine = lineForPosition(anc);
  1152. if (anc < pos) {
  1153. pos = lastPositionInLine(posLine);
  1154. anc = firstPositionInLine(ancLine);
  1155. } else {
  1156. pos = firstPositionInLine(posLine);
  1157. anc = lastPositionInLine(ancLine);
  1158. }
  1159. setAnchorAndPosition(anc, pos);
  1160. } else if (m_visualMode == VisualCharMode) {
  1161. /* Nothing */
  1162. } else {
  1163. QTC_CHECK(false);
  1164. }
  1165. } else {
  1166. setAnchorAndPosition(pos, pos);
  1167. }
  1168. m_oldExternalPosition = position();
  1169. m_oldExternalAnchor = anchor();
  1170. m_oldVisualMode = m_visualMode;
  1171. }
  1172. void FakeVimHandler::Private::importSelection()
  1173. {
  1174. bool hasBlock = false;
  1175. emit q->requestHasBlockSelection(&hasBlock);
  1176. if (position() == m_oldExternalPosition
  1177. && anchor() == m_oldExternalAnchor) {
  1178. // Undo drawing correction.
  1179. m_visualMode = m_oldVisualMode;
  1180. setAnchorAndPosition(m_oldInternalAnchor, m_oldInternalPosition);
  1181. //setMark('<', m_oldInternalAnchor);
  1182. //setMark('>', m_oldInternalPosition);
  1183. } else {
  1184. // Import new selection.
  1185. Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
  1186. if (cursor().hasSelection()) {
  1187. if (mods & RealControlModifier)
  1188. m_visualMode = VisualBlockMode;
  1189. else if (mods & Qt::AltModifier)
  1190. m_visualMode = VisualBlockMode;
  1191. else if (mods & Qt::ShiftModifier)
  1192. m_visualMode = VisualLineMode;
  1193. else
  1194. m_visualMode = VisualCharMode;
  1195. } else {
  1196. m_visualMode = NoVisualMode;
  1197. }
  1198. //dump("IS @");
  1199. //setMark('<', tc.anchor());
  1200. //setMark('>', tc.position());
  1201. }
  1202. }
  1203. void FakeVimHandler::Private::updateEditor()
  1204. {
  1205. const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
  1206. EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
  1207. setupCharClass();
  1208. }
  1209. void FakeVimHandler::Private::restoreWidget(int tabSize)
  1210. {
  1211. //showBlackMessage(QString());
  1212. //updateMiniBuffer();
  1213. //EDITOR(removeEventFilter(q));
  1214. //EDITOR(setReadOnly(m_wasReadOnly));
  1215. const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
  1216. EDITOR(setTabStopWidth(charWidth * tabSize));
  1217. m_visualMode = NoVisualMode;
  1218. // Force "ordinary" cursor.
  1219. m_mode = InsertMode;
  1220. m_submode = NoSubMode;
  1221. m_subsubmode = NoSubSubMode;
  1222. updateCursorShape();
  1223. updateSelection();
  1224. }
  1225. EventResult FakeVimHandler::Private::handleKey(const Input &input)
  1226. {
  1227. KEY_DEBUG("HANDLE INPUT: " << input << " MODE: " << mode);
  1228. if (m_mode == ExMode)
  1229. return handleExMode(input);
  1230. if (m_subsubmode == SearchSubSubMode)
  1231. return handleSearchSubSubMode(input);
  1232. if (m_mode == InsertMode || m_mode == ReplaceMode || m_mode == CommandMode) {
  1233. g.pendingInput.append(input);
  1234. const char code = m_mode == InsertMode ? 'i' : 'n';
  1235. if (g.mappings.value(code).mappingDone(&g.pendingInput))
  1236. return handleKey2();
  1237. if (g.inputTimer != -1)
  1238. killTimer(g.inputTimer);
  1239. g.inputTimer = startTimer(1000);
  1240. return EventHandled;
  1241. }
  1242. return EventUnhandled;
  1243. }
  1244. EventResult FakeVimHandler::Private::handleKey2()
  1245. {
  1246. Inputs pendingInput = g.pendingInput;
  1247. g.pendingInput.clear();
  1248. if (m_mode == InsertMode) {
  1249. EventResult result = EventUnhandled;
  1250. foreach (const Input &in, pendingInput) {
  1251. EventResult r = handleInsertMode(in);
  1252. if (r == EventHandled)
  1253. result = EventHandled;
  1254. }
  1255. return result;
  1256. }
  1257. if (m_mode == ReplaceMode) {
  1258. EventResult result = EventUnhandled;
  1259. foreach (const Input &in, pendingInput) {
  1260. EventResult r = handleReplaceMode(in);
  1261. if (r == EventHandled)
  1262. result = EventHandled;
  1263. }
  1264. return result;
  1265. }
  1266. if (m_mode == CommandMode) {
  1267. EventResult result = EventUnhandled;
  1268. foreach (const Input &in, pendingInput) {
  1269. EventResult r = handleCommandMode(in);
  1270. if (r == EventHandled)
  1271. result = EventHandled;
  1272. }
  1273. return result;
  1274. }
  1275. return EventUnhandled;
  1276. }
  1277. void FakeVimHandler::Private::timerEvent(QTimerEvent *ev)
  1278. {
  1279. Q_UNUSED(ev);
  1280. handleKey2();
  1281. }
  1282. void FakeVimHandler::Private::stopIncrementalFind()
  1283. {
  1284. if (m_findPending) {
  1285. m_findPending = false;
  1286. QTextCursor tc = cursor();
  1287. setAnchorAndPosition(m_findStartPosition, tc.selectionStart());
  1288. finishMovement();
  1289. setAnchor();
  1290. }
  1291. }
  1292. void FakeVimHandler::Private::setUndoPosition()
  1293. {
  1294. int pos = qMin(position(), anchor());
  1295. if (m_visualMode == VisualLineMode)
  1296. pos = firstPositionInLine(lineForPosition(pos));
  1297. const int rev = document()->availableUndoSteps();
  1298. m_undoCursorPosition[rev] = pos;
  1299. }
  1300. void FakeVimHandler::Private::moveDown(int n)
  1301. {
  1302. #if 0
  1303. // does not work for "hidden" documents like in the autotests
  1304. tc.movePosition(Down, MoveAnchor, n);
  1305. #else
  1306. const int col = position() - block().position();
  1307. const int lastLine = document()->lastBlock().blockNumber();
  1308. const int targetLine = qMax(0, qMin(lastLine, block().blockNumber() + n));
  1309. const QTextBlock &block = document()->findBlockByNumber(targetLine);
  1310. const int pos = block.position();
  1311. setPosition(pos + qMax(0, qMin(block.length() - 2, col)));
  1312. moveToTargetColumn();
  1313. #endif
  1314. }
  1315. void FakeVimHandler::Private::moveToEndOfLine()
  1316. {
  1317. #if 0
  1318. // does not work for "hidden" documents like in the autotests
  1319. tc.movePosition(EndOfLine, MoveAnchor);
  1320. #else
  1321. const int pos = block().position() + block().length() - 2;
  1322. setPosition(qMax(block().position(), pos));
  1323. #endif
  1324. }
  1325. void FakeVimHandler::Private::moveBehindEndOfLine()
  1326. {
  1327. int pos = qMin(block().position() + block().length() - 1,
  1328. lastPositionInDocument());
  1329. setPosition(pos);
  1330. }
  1331. void FakeVimHandler::Private::moveToStartOfLine()
  1332. {
  1333. #if 0
  1334. // does not work for "hidden" documents like in the autotests
  1335. tc.movePosition(StartOfLine, MoveAnchor);
  1336. #else
  1337. setPosition(block().position());
  1338. #endif
  1339. }
  1340. void FakeVimHandler::Private::finishMovement(const QString &dotCommand, int count)
  1341. {
  1342. finishMovement(dotCommand.arg(count));
  1343. }
  1344. void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
  1345. {
  1346. //dump("FINISH MOVEMENT");
  1347. if (m_submode == FilterSubMode) {
  1348. int beginLine = lineForPosition(anchor());
  1349. int endLine = lineForPosition(position());
  1350. setPosition(qMin(anchor(), position()));
  1351. enterExMode();
  1352. m_currentMessage.clear();
  1353. m_commandBuffer.setContents(QString(".,+%1!").arg(qAbs(endLine - beginLine)));
  1354. //g.commandHistory.append(QString());
  1355. updateMiniBuffer();
  1356. return;
  1357. }
  1358. //if (isVisualMode())
  1359. // setMark('>', position());
  1360. if (m_submode == ChangeSubMode
  1361. || m_submode == DeleteSubMode
  1362. || m_submode == YankSubMode
  1363. || m_submode == TransformSubMode) {
  1364. if (m_submode != YankSubMode)
  1365. beginEditBlock();
  1366. if (m_movetype == MoveLineWise)
  1367. m_rangemode = (m_submode == ChangeSubMode)
  1368. ? RangeLineModeExclusive
  1369. : RangeLineMode;
  1370. if (m_movetype == MoveInclusive) {
  1371. if (anchor() <= position()) {
  1372. if (!cursor().atBlockEnd())
  1373. setPosition(position() + 1); // correction
  1374. } else {
  1375. setAnchorAndPosition(anchor() + 1, position());
  1376. }
  1377. }
  1378. if (m_positionPastEnd) {
  1379. const int anc = anchor();
  1380. moveBehindEndOfLine();
  1381. moveRight();
  1382. setAnchorAndPosition(anc, position());
  1383. }
  1384. if (m_anchorPastEnd) {
  1385. setAnchorAndPosition(anchor() + 1, position());
  1386. }
  1387. if (m_submode != TransformSubMode) {
  1388. yankText(currentRange(), m_register);
  1389. if (m_movetype == MoveLineWise)
  1390. setRegisterRangeMode(m_register, RangeLineMode);
  1391. }
  1392. m_positionPastEnd = m_anchorPastEnd = false;
  1393. }
  1394. if (m_submode == ChangeSubMode) {
  1395. if (m_rangemode == RangeLineMode)
  1396. m_rangemode = RangeLineModeExclusive;
  1397. removeText(currentRange());
  1398. if (!dotCommand.isEmpty())
  1399. setDotCommand(QLatin1Char('c') + dotCommand);
  1400. if (m_movetype == MoveLineWise)
  1401. insertAutomaticIndentation(true);
  1402. endEditBlock();
  1403. enterInsertMode();
  1404. m_submode = NoSubMode;
  1405. } else if (m_submode == DeleteSubMode) {
  1406. removeText(currentRange());
  1407. if (!dotCommand.isEmpty())
  1408. setDotCommand(QLatin1Char('d') + dotCommand);
  1409. if (m_movetype == MoveLineWise)
  1410. handleStartOfLine();
  1411. m_submode = NoSubMode;
  1412. if (atEndOfLine())
  1413. moveLeft();
  1414. else
  1415. setTargetColumn();
  1416. endEditBlock();
  1417. } else if (m_submode == YankSubMode) {
  1418. m_submode = NoSubMode;
  1419. const int la = lineForPosition(anchor());
  1420. const int lp = lineForPosition(position());
  1421. if (m_register != '"') {
  1422. setPosition(mark(m_register));
  1423. moveToStartOfLine();
  1424. } else {
  1425. if (anchor() <= position())
  1426. setPosition(anchor());
  1427. }
  1428. if (la != lp)
  1429. showBlackMessage(QString("%1 lines yanked").arg(qAbs(la - lp) + 1));
  1430. } else if (m_submode == TransformSubMode) {
  1431. if (m_subsubmode == InvertCaseSubSubMode) {
  1432. invertCase(currentRange());
  1433. if (!dotCommand.isEmpty())
  1434. setDotCommand(QLatin1Char('~') + dotCommand);
  1435. } else if (m_subsubmode == UpCaseSubSubMode) {
  1436. upCase(currentRange());
  1437. if (!dotCommand.isEmpty())
  1438. setDotCommand("gU" + dotCommand);
  1439. } else if (m_subsubmode == DownCaseSubSubMode) {
  1440. downCase(currentRange());
  1441. if (!dotCommand.isEmpty())
  1442. setDotCommand("gu" + dotCommand);
  1443. }
  1444. m_submode = NoSubMode;
  1445. m_subsubmode = NoSubSubMode;
  1446. setPosition(qMin(anchor(), position()));
  1447. if (m_movetype == MoveLineWise)
  1448. handleStartOfLine();
  1449. endEditBlock();
  1450. } else if (m_submode == IndentSubMode) {
  1451. setUndoPosition();
  1452. recordJump();
  1453. indentSelectedText();
  1454. m_submode = NoSubMode;
  1455. } else if (m_submode == ShiftRightSubMode) {
  1456. setUndoPosition();
  1457. recordJump();
  1458. shiftRegionRight(1);
  1459. m_submode = NoSubMode;
  1460. } else if (m_submode == ShiftLeftSubMode) {
  1461. setUndoPosition();
  1462. recordJump();
  1463. shiftRegionLeft(1);
  1464. m_submode = NoSubMode;
  1465. }
  1466. resetCommandMode();
  1467. updateSelection();
  1468. updateMiniBuffer();
  1469. }
  1470. void FakeVimHandler::Private::resetCommandMode()
  1471. {
  1472. m_movetype = MoveInclusive;
  1473. m_mvcount.clear();
  1474. m_opcount.clear();
  1475. m_gflag = false;
  1476. m_register = '"';
  1477. //m_tc.clearSelection();
  1478. m_rangemode = RangeCharMode;
  1479. }
  1480. void FakeVimHandler::Private::updateSelection()
  1481. {
  1482. QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
  1483. if (!m_searchCursor.isNull()) {
  1484. QTextEdit::ExtraSelection sel;
  1485. sel.cursor = m_searchCursor;
  1486. sel.format = m_searchCursor.blockCharFormat();
  1487. sel.format.setForeground(Qt::white);
  1488. sel.format.setBackground(Qt::black);
  1489. selections.append(sel);
  1490. }
  1491. if (hasConfig(ConfigShowMarks)) {
  1492. for (MarksIterator it(m_marks); it.hasNext(); ) {
  1493. it.next();
  1494. QTextEdit::ExtraSelection sel;
  1495. const int pos = it.value().position();
  1496. sel.cursor = cursor();
  1497. sel.cursor.setPosition(pos, MoveAnchor);
  1498. sel.cursor.setPosition(pos + 1, KeepAnchor);
  1499. sel.format = cursor().blockCharFormat();
  1500. sel.format.setForeground(Qt::blue);
  1501. sel.format.setBackground(Qt::green);
  1502. selections.append(sel);
  1503. }
  1504. }
  1505. //qDebug() << "SELECTION: " << selections;
  1506. emit q->selectionChanged(selections);
  1507. }
  1508. void FakeVimHandler::Private::updateMiniBuffer()
  1509. {
  1510. if (!m_textedit && !m_plaintextedit)
  1511. return;
  1512. QString msg;
  1513. int cursorPos = -1;
  1514. if (m_passing) {
  1515. msg = "-- PASSING -- ";
  1516. } else if (!m_currentMessage.isEmpty()) {
  1517. msg = m_currentMessage;
  1518. } else if (m_mode == CommandMode && isVisualMode()) {
  1519. if (isVisualCharMode()) {
  1520. msg = "-- VISUAL --";
  1521. } else if (isVisualLineMode()) {
  1522. msg = "-- VISUAL LINE --";
  1523. } else if (isVisualBlockMode()) {
  1524. msg = "-- VISUAL BLOCK --";
  1525. }
  1526. } else if (m_mode == InsertMode) {
  1527. msg = "-- INSERT --";
  1528. } else if (m_mode == ReplaceMode) {
  1529. msg = "-- REPLACE --";
  1530. } else if (!m_commandPrefix.isEmpty()) {
  1531. //QTC_ASSERT(m_mode == ExMode || m_subsubmode == SearchSubSubMode,
  1532. // qDebug() << "MODE: " << m_mode << m_subsubmode);
  1533. msg = m_commandPrefix + m_commandBuffer.display();
  1534. if (m_mode != CommandMode)
  1535. cursorPos = m_commandPrefix.size() + m_commandBuffer.cursorPos();
  1536. } else {
  1537. QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode);
  1538. msg = "-- COMMAND --";
  1539. }
  1540. emit q->commandBufferChanged(msg, cursorPos);
  1541. int linesInDoc = linesInDocument();
  1542. int l = cursorLine();
  1543. QString status;
  1544. const QString pos = QString::fromLatin1("%1,%2")
  1545. .arg(l + 1).arg(physicalCursorColumn() + 1);
  1546. // FIXME: physical "-" logical
  1547. if (linesInDoc != 0) {
  1548. status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
  1549. } else {
  1550. status = FakeVimHandler::tr("%1All").arg(pos, -10);
  1551. }
  1552. emit q->statusDataChanged(status);
  1553. }
  1554. void FakeVimHandler::Private::showRedMessage(const QString &msg)
  1555. {
  1556. //qDebug() << "MSG: " << msg;
  1557. m_currentMessage = msg;
  1558. updateMiniBuffer();
  1559. }
  1560. void FakeVimHandler::Private::showBlackMessage(const QString &msg)
  1561. {
  1562. //qDebug() << "MSG: " << msg;
  1563. m_commandBuffer.setContents(msg);
  1564. updateMiniBuffer();
  1565. }
  1566. void FakeVimHandler::Private::notImplementedYet()
  1567. {
  1568. qDebug() << "Not implemented in FakeVim";
  1569. showRedMessage(FakeVimHandler::tr("Not implemented in FakeVim"));
  1570. updateMiniBuffer();
  1571. }
  1572. void FakeVimHandler::Private::passShortcuts(bool enable)
  1573. {
  1574. m_passing = enable;
  1575. updateMiniBuffer();
  1576. if (enable)
  1577. QCoreApplication::instance()->installEventFilter(q);
  1578. else
  1579. QCoreApplication::instance()->removeEventFilter(q);
  1580. }
  1581. static bool subModeCanUseTextObjects(int submode)
  1582. {
  1583. return submode == DeleteSubMode
  1584. || submode == YankSubMode
  1585. || submode == ChangeSubMode
  1586. || submode == IndentSubMode
  1587. || submode == ShiftLeftSubMode
  1588. || submode == ShiftRightSubMode;
  1589. }
  1590. EventResult FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
  1591. {
  1592. //const int key = input.key;
  1593. EventResult handled = EventHandled;
  1594. if (m_subsubmode == FtSubSubMode) {
  1595. m_semicolonType = m_subsubdata;
  1596. m_semicolonKey = input.text();
  1597. bool valid = handleFfTt(m_semicolonKey);
  1598. m_subsubmode = NoSubSubMode;
  1599. if (!valid) {
  1600. m_submode = NoSubMode;
  1601. finishMovement();
  1602. } else {
  1603. finishMovement(QString("%1%2%3")
  1604. .arg(count())
  1605. .arg(m_semicolonType.text())
  1606. .arg(m_semicolonKey));
  1607. }
  1608. } else if (m_subsubmode == TextObjectSubSubMode) {
  1609. if (input.is('w'))
  1610. selectWordTextObject(m_subsubdata.is('i'));
  1611. else if (input.is('W'))
  1612. selectWORDTextObject(m_subsubdata.is('i'));
  1613. else if (input.is('s'))
  1614. selectSentenceTextObject(m_subsubdata.is('i'));
  1615. else if (input.is('p'))
  1616. selectParagraphTextObject(m_subsubdata.is('i'));
  1617. else if (input.is('[') || input.is(']'))
  1618. selectBlockTextObject(m_subsubdata.is('i'), '[', ']');
  1619. else if (input.is('(') || input.is(')') || input.is('b'))
  1620. selectBlockTextObject(m_subsubdata.is('i'), '(', ')');
  1621. else if (input.is('<') || input.is('>'))
  1622. selectBlockTextObject(m_subsubdata.is('i'), '<', '>');
  1623. else if (input.is('{') || input.is('}') || input.is('B'))
  1624. selectBlockTextObject(m_subsubdata.is('i'), '{', '}');
  1625. else if (input.is('"') || input.is('\'') || input.is('`'))
  1626. selectQuotedStringTextObject(m_subsubdata.is('i'), input.key());
  1627. m_subsubmode = NoSubSubMode;
  1628. finishMovement(QString("%1%2%3")
  1629. .arg(count())
  1630. .arg(m_subsubdata.text())
  1631. .arg(input.text()));
  1632. } else if (m_subsubmode == MarkSubSubMode) {
  1633. setMark(input.asChar().unicode(), position());
  1634. m_subsubmode = NoSubSubMode;
  1635. } else if (m_subsubmode == BackTickSubSubMode
  1636. || m_subsubmode == TickSubSubMode) {
  1637. int m = mark(input.asChar().unicode());
  1638. if (m != -1) {
  1639. setPosition(m);
  1640. if (m_subsubmode == TickSubSubMode)
  1641. moveToFirstNonBlankOnLine();
  1642. finishMovement();
  1643. } else {
  1644. showRedMessage(msgMarkNotSet(input.text()));
  1645. }
  1646. m_subsubmode = NoSubSubMode;
  1647. } else {
  1648. handled = EventUnhandled;
  1649. }
  1650. return handled;
  1651. }
  1652. EventResult FakeVimHandler::Private::handleOpenSquareSubMode(const Input &input)
  1653. {
  1654. EventResult handled = EventHandled;
  1655. m_submode = NoSubMode;
  1656. if (input.is('{')) {
  1657. searchBalanced(false, '{', '}');
  1658. } else if (input.is('(')) {
  1659. searchBalanced(false, '(', ')');
  1660. } else {
  1661. handled = EventUnhandled;
  1662. }
  1663. return handled;
  1664. }
  1665. EventResult FakeVimHandler::Private::handleCloseSquareSubMode(const Input &input)
  1666. {
  1667. EventResult handled = EventHandled;
  1668. m_submode = NoSubMode;
  1669. if (input.is('}')) {
  1670. searchBalanced(true, '}', '{');
  1671. } else if (input.is(')')) {
  1672. searchBalanced(true, ')', '(');
  1673. } else {
  1674. handled = EventUnhandled;
  1675. }
  1676. return handled;
  1677. }
  1678. EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
  1679. {
  1680. EventResult handled = EventHandled;
  1681. if (input.isEscape()) {
  1682. if (isVisualMode()) {
  1683. leaveVisualMode();
  1684. } else if (m_submode != NoSubMode) {
  1685. m_submode = NoSubMode;
  1686. m_subsubmode = NoSubSubMode;
  1687. finishMovement();
  1688. } else {
  1689. resetCommandMode();
  1690. updateSelection();
  1691. updateMiniBuffer();
  1692. }
  1693. } else if (m_subsubmode != NoSubSubMode) {
  1694. handleCommandSubSubMode(input);
  1695. } else if (m_submode == OpenSquareSubMode) {
  1696. handled = handleOpenSquareSubMode(input);
  1697. } else if (m_submode == CloseSquareSubMode) {
  1698. handled = handleCloseSquareSubMode(input);
  1699. } else if (m_submode == WindowSubMode) {
  1700. emit q->windowCommandRequested(input.key());
  1701. m_submode = NoSubMode;
  1702. } else if (m_submode == RegisterSubMode) {
  1703. m_register = input.asChar().unicode();
  1704. m_submode = NoSubMode;
  1705. m_rangemode = RangeLineMode;
  1706. } else if (m_submode == ReplaceSubMode) {
  1707. if (isVisualMode()) {
  1708. setUndoPosition();
  1709. m_lastChangePosition = position();
  1710. if (isVisualLineMode())
  1711. m_rangemode = RangeLineMode;
  1712. else if (isVisualBlockMode())
  1713. m_rangemode = RangeBlockMode;
  1714. else
  1715. m_rangemode = RangeCharMode;
  1716. leaveVisualMode();
  1717. Range range = currentRange();
  1718. Transformation tr =
  1719. &FakeVimHandler::Private::replaceByCharTransform;
  1720. transformText(range, tr, input.asChar());
  1721. setPosition(range.beginPos);
  1722. } else if (count() <= rightDist()) {
  1723. setUndoPosition();
  1724. m_lastChangePosition = position();
  1725. setAnchor();
  1726. moveRight(count());
  1727. Range range = currentRange();
  1728. if (input.isReturn()) {
  1729. beginEditBlock();
  1730. replaceText(range, QString());
  1731. insertText(QString("\n"));
  1732. endEditBlock();
  1733. } else {
  1734. replaceText(range, QString(count(), input.asChar()));
  1735. moveLeft();
  1736. }
  1737. setTargetColumn();
  1738. setDotCommand("%1r" + input.text(), count());
  1739. }
  1740. m_submode = NoSubMode;
  1741. finishMovement();
  1742. } else if (m_submode == ChangeSubMode && input.is('c')) { // tested
  1743. setUndoPosition();
  1744. m_lastChangePosition = position();
  1745. moveToStartOfLine();
  1746. setAnchor();
  1747. moveDown(count() - 1);
  1748. moveToEndOfLine();
  1749. m_movetype = MoveLineWise;
  1750. m_lastInsertion.clear();
  1751. setDotCommand("%1cc", count());
  1752. finishMovement();
  1753. } else if (m_submode == DeleteSubMode && input.is('d')) { // tested
  1754. setUndoPosition();
  1755. m_lastChangePosition = position();
  1756. m_movetype = MoveLineWise;
  1757. int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
  1758. Range range(position(), endPos, RangeLineMode);
  1759. yankText(range);
  1760. removeText(range);
  1761. setDotCommand("%1dd", count());
  1762. m_submode = NoSubMode;
  1763. handleStartOfLine();
  1764. setTargetColumn();
  1765. finishMovement();
  1766. } else if ((subModeCanUseTextObjects(m_submode) || isVisualMode())
  1767. && (input.is('a') || input.is('i'))) {
  1768. m_subsubmode = TextObjectSubSubMode;
  1769. m_subsubdata = input;
  1770. } else if (m_submode == ShiftLeftSubMode && input.is('<')) {
  1771. m_lastChangePosition = position();
  1772. setAnchor();
  1773. moveDown(count() - 1);
  1774. m_movetype = MoveLineWise;
  1775. setDotCommand("%1<<", count());
  1776. finishMovement();
  1777. } else if (m_submode == ShiftRightSubMode && input.is('>')) {
  1778. m_lastChangePosition = position();
  1779. setAnchor();
  1780. moveDown(count() - 1);
  1781. m_movetype = MoveLineWise;
  1782. setDotCommand("%1>>", count());
  1783. finishMovement();
  1784. } else if (m_submode == IndentSubMode && input.is('=')) {
  1785. m_lastChangePosition = position();
  1786. setAnchor();
  1787. moveDown(count() - 1);
  1788. m_movetype = MoveLineWise;
  1789. setDotCommand("%1==", count());
  1790. finishMovement();
  1791. } else if (m_submode == ZSubMode) {
  1792. //qDebug() << "Z_MODE " << cursorLine() << linesOnScreen();
  1793. if (input.isReturn() || input.is('t')) {
  1794. // Cursor line to top of window.
  1795. if (!m_mvcount.isEmpty())
  1796. setPosition(firstPositionInLine(count()));
  1797. scrollUp(- cursorLineOnScreen());
  1798. if (input.isReturn())
  1799. moveToFirstNonBlankOnLine();
  1800. finishMovement();
  1801. } else if (input.is('.') || input.is('z')) {
  1802. // Cursor line to center of window.
  1803. if (!m_mvcount.isEmpty())
  1804. setPosition(firstPositionInLine(count()));
  1805. scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
  1806. if (input.is('.'))
  1807. moveToFirstNonBlankOnLine();
  1808. finishMovement();
  1809. } else if (input.is('-') || input.is('b')) {
  1810. // Cursor line to bottom of window.
  1811. if (!m_mvcount.isEmpty())
  1812. setPosition(firstPositionInLine(count()));
  1813. scrollUp(linesOnScreen() - cursorLineOnScreen());
  1814. if (input.is('-'))
  1815. moveToFirstNonBlankOnLine();
  1816. finishMovement();
  1817. } else {
  1818. qDebug() << "IGNORED Z_MODE " << input.key() << input.text();
  1819. }
  1820. m_submode = NoSubMode;
  1821. } else if (m_submode == CapitalZSubMode) {
  1822. // Recognize ZZ and ZQ as aliases for ":x" and ":q!".
  1823. m_submode = NoSubMode;
  1824. if (input.is('Z'))
  1825. handleExCommand(QString(QLatin1Char('x')));
  1826. else if (input.is('Q'))
  1827. handleExCommand("q!");
  1828. } else if (input.isDigit()) {
  1829. if (input.is('0') && m_mvcount.isEmpty()) {
  1830. m_movetype = MoveExclusive;
  1831. moveToStartOfLine();
  1832. setTargetColumn();
  1833. finishMovement(QString(QLatin1Char('0')));
  1834. } else {
  1835. m_mvcount.append(input.text());
  1836. }
  1837. } else {
  1838. handled = handleCommandMode1(input);
  1839. }
  1840. return handled;
  1841. }
  1842. EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
  1843. {
  1844. EventResult handled = EventHandled;
  1845. if (input.is('^') || input.is('_')) {
  1846. moveToFirstNonBlankOnLine();
  1847. setTargetColumn();
  1848. m_movetype = MoveExclusive;
  1849. finishMovement(input.text());
  1850. } else if (0 && input.is(',')) {
  1851. // FIXME: fakevim uses ',' by itself, so it is incompatible
  1852. m_subsubmode = FtSubSubMode;
  1853. // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
  1854. //m_subsubdata = m_semicolonType ^ 32;
  1855. handleFfTt(m_semicolonKey);
  1856. m_subsubmode = NoSubSubMode;
  1857. finishMovement();
  1858. } else if (input.is(';')) {
  1859. m_subsubmode = FtSubSubMode;
  1860. m_subsubdata = m_semicolonType;
  1861. handleFfTt(m_semicolonKey);
  1862. m_subsubmode = NoSubSubMode;
  1863. finishMovement();
  1864. } else if (input.is('&')) {
  1865. handleExCommand(m_gflag ? "%s//~/&" : "s");
  1866. } else if (input.is(':')) {
  1867. enterExMode();
  1868. g.commandHistory.restart();
  1869. m_currentMessage.clear();
  1870. m_commandBuffer.clear();
  1871. if (isVisualMode())
  1872. m_commandBuffer.setContents("'<,'>");
  1873. updateMiniBuffer();
  1874. } else if (input.is('/') || input.is('?')) {
  1875. m_lastSearchForward = input.is('/');
  1876. g.searchHistory.restart();
  1877. if (hasConfig(ConfigUseCoreSearch)) {
  1878. // re-use the core dialog.
  1879. m_findPending = true;
  1880. m_findStartPosition = position();
  1881. m_movetype = MoveExclusive;
  1882. setAnchor(); // clear selection: otherwise, search is restricted to selection
  1883. emit q->findRequested(!m_lastSearchForward);
  1884. } else {
  1885. // FIXME: make core find dialog sufficiently flexible to
  1886. // produce the "default vi" behaviour too. For now, roll our own.
  1887. m_currentMessage.clear();
  1888. m_movetype = MoveExclusive;
  1889. m_subsubmode = SearchSubSubMode;
  1890. m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?');
  1891. m_commandBuffer.clear();
  1892. updateMiniBuffer();
  1893. }
  1894. } else if (input.is('`')) {
  1895. m_subsubmode = BackTickSubSubMode;
  1896. if (m_submode != NoSubMode)
  1897. m_movetype = MoveLineWise;
  1898. } else if (input.is('#') || input.is('*')) {
  1899. // FIXME: That's not proper vim behaviour
  1900. QString needle;
  1901. QTextCursor tc = cursor();
  1902. tc.select(QTextCursor::WordUnderCursor);
  1903. needle = "\\<" + tc.selection().toPlainText() + "\\>";
  1904. setAnchorAndPosition(tc.position(), tc.anchor());
  1905. g.searchHistory.append(needle);
  1906. m_lastSearchForward = input.is('*');
  1907. m_currentMessage.clear();
  1908. m_commandPrefix = QLatin1Char(m_lastSearchForward ? '/' : '?');
  1909. m_commandBuffer.setContents(needle);
  1910. SearchData sd;
  1911. sd.needle = needle;
  1912. sd.forward = m_lastSearchForward;
  1913. sd.highlightCursor = false;
  1914. sd.highlightMatches = true;
  1915. search(sd);
  1916. //m_searchCursor = QTextCursor();
  1917. //updateSelection();
  1918. //updateMiniBuffer();
  1919. } else if (input.is('\'')) {
  1920. m_subsubmode = TickSubSubMode;
  1921. if (m_submode != NoSubMode)
  1922. m_movetype = MoveLineWise;
  1923. } else if (input.is('|')) {
  1924. moveToStartOfLine();
  1925. moveRight(qMin(count(), rightDist()) - 1);
  1926. setTargetColumn();
  1927. finishMovement();
  1928. } else if (input.is('!') && isNoVisualMode()) {
  1929. m_submode = FilterSubMode;
  1930. } else if (input.is('!') && isVisualMode()) {
  1931. enterExMode();
  1932. m_currentMessage.clear();
  1933. m_commandBuffer.setContents("'<,'>!");
  1934. //g.commandHistory.append(QString());
  1935. updateMiniBuffer();
  1936. } else if (input.is('"')) {
  1937. m_submode = RegisterSubMode;
  1938. } else if (input.isReturn()) {
  1939. moveToStartOfLine();
  1940. moveDown();
  1941. moveToFirstNonBlankOnLine();
  1942. m_movetype = MoveLineWise;
  1943. finishMovement("%1j", count());
  1944. } else if (input.is('-')) {
  1945. moveToStartOfLine();
  1946. moveUp(count());
  1947. moveToFirstNonBlankOnLine();
  1948. m_movetype = MoveLineWise;
  1949. finishMovement("%1-", count());
  1950. } else if (input.is('+')) {
  1951. moveToStartOfLine();
  1952. moveDown(count());
  1953. moveToFirstNonBlankOnLine();
  1954. m_movetype = MoveLineWise;
  1955. finishMovement("%1+", count());
  1956. } else if (input.isKey(Key_Home)) {
  1957. moveToStartOfLine();
  1958. setTargetColumn();
  1959. finishMovement();
  1960. } else if (input.is('$') || input.isKey(Key_End)) {
  1961. if (count() > 1)
  1962. moveDown(count() - 1);
  1963. moveToEndOfLine();
  1964. m_movetype = MoveInclusive;
  1965. setTargetColumn();
  1966. if (m_submode == NoSubMode)
  1967. m_targetColumn = -1;
  1968. if (isVisualMode())
  1969. m_visualTargetColumn = -1;
  1970. finishMovement("%1$", count());
  1971. } else if (input.is(',')) {
  1972. passShortcuts(true);
  1973. } else if (input.is('.')) {
  1974. //qDebug() << "REPEATING" << quoteUnprintable(g.dotCommand) << count()
  1975. // << input;
  1976. QString savedCommand = g.dotCommand;
  1977. g.dotCommand.clear();
  1978. replay(savedCommand, count());
  1979. enterCommandMode();
  1980. g.dotCommand = savedCommand;
  1981. } else if (input.is('<')) {
  1982. setUndoPosition();
  1983. if (isNoVisualMode()) {
  1984. m_submode = ShiftLeftSubMode;
  1985. } else {
  1986. shiftRegionLeft(count());
  1987. leaveVisualMode();
  1988. }
  1989. } else if (input.is('>')) {
  1990. setUndoPosition();
  1991. if (isNoVisualMode()) {
  1992. m_submode = ShiftRightSubMode;
  1993. } else {
  1994. shiftRegionRight(count());
  1995. leaveVisualMode();
  1996. }
  1997. } else if (input.is('=')) {
  1998. setUndoPosition();
  1999. if (isNoVisualMode()) {
  2000. m_submode = IndentSubMode;
  2001. } else {
  2002. indentSelectedText();
  2003. leaveVisualMode();
  2004. }
  2005. } else if (input.is('%')) {
  2006. moveToMatchingParanthesis();
  2007. finishMovement();
  2008. } else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) {
  2009. leaveVisualMode();
  2010. setUndoPosition();
  2011. breakEditBlock();
  2012. enterInsertMode();
  2013. m_lastInsertion.clear();
  2014. if (!atEndOfLine())
  2015. moveRight();
  2016. updateMiniBuffer();
  2017. } else if (input.is('A')) {
  2018. setUndoPosition();
  2019. breakEditBlock();
  2020. moveBehindEndOfLine();
  2021. setAnchor();
  2022. enterInsertMode();
  2023. setDotCommand(QString(QLatin1Char('A')));
  2024. m_lastInsertion.clear();
  2025. updateMiniBuffer();
  2026. } else if (input.isControl('a')) {
  2027. changeNumberTextObject(true);
  2028. } else if (input.is('b') || input.isShift(Key_Left)) {
  2029. m_movetype = MoveExclusive;
  2030. moveToWordBoundary(false, false);
  2031. setTargetColumn();
  2032. finishMovement();
  2033. } else if (input.is('B')) {
  2034. m_movetype = MoveExclusive;
  2035. moveToWordBoundary(true, false);
  2036. setTargetColumn();
  2037. finishMovement();
  2038. } else if (input.is('c') && isNoVisualMode()) {
  2039. setUndoPosition();
  2040. if (atEndOfLine())
  2041. moveLeft();
  2042. setAnchor();
  2043. m_submode = ChangeSubMode;
  2044. } else if ((input.is('c') || input.is('C') || input.is('s') || input.is('R'))
  2045. && (isVisualCharMode() || isVisualLineMode())) {
  2046. if ((input.is('c')|| input.is('s')) && isVisualCharMode()) {
  2047. leaveVisualMode();
  2048. m_rangemode = RangeCharMode;
  2049. } else {
  2050. leaveVisualMode();
  2051. m_rangemode = RangeLineMode;
  2052. // leaveVisualMode() has set this to MoveInclusive for visual character mode
  2053. m_movetype = MoveLineWise;
  2054. }
  2055. m_submode = ChangeSubMode;
  2056. finishMovement();
  2057. } else if (input.is('C')) {
  2058. setAnchor();
  2059. moveToEndOfLine();
  2060. m_submode = ChangeSubMode;
  2061. setDotCommand(QString(QLatin1Char('C')));
  2062. finishMovement();
  2063. } else if (input.isControl('c')) {
  2064. if (isNoVisualMode())
  2065. showBlackMessage("Type Alt-v,Alt-v to quit FakeVim mode");
  2066. else
  2067. leaveVisualMode();
  2068. } else if (input.is('d') && isNoVisualMode()) {
  2069. if (m_rangemode == RangeLineMode) {
  2070. int pos = position();
  2071. moveToEndOfLine();
  2072. setAnchor();
  2073. setPosition(pos);
  2074. } else {
  2075. setAnchor();
  2076. }
  2077. m_opcount = m_mvcount;
  2078. m_mvcount.clear();
  2079. m_submode = DeleteSubMode;
  2080. } else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete))
  2081. && isVisualMode()) {
  2082. if (isVisualCharMode()) {
  2083. leaveVisualMode();
  2084. m_submode = DeleteSubMode;
  2085. finishMovement();
  2086. } else if (isVisualLineMode()) {
  2087. leaveVisualMode();
  2088. m_rangemode = RangeLineMode;
  2089. yankText(currentRange(), m_register);
  2090. removeText(currentRange());
  2091. handleStartOfLine();
  2092. } else if (isVisualBlockMode()) {
  2093. leaveVisualMode();
  2094. m_rangemode = RangeBlockMode;
  2095. yankText(currentRange(), m_register);
  2096. removeText(currentRange());
  2097. setPosition(qMin(position(), anchor()));
  2098. }
  2099. } else if (input.is('D') && isNoVisualMode()) {
  2100. if (atEndOfLine())
  2101. moveLeft();
  2102. m_submode = DeleteSubMode;
  2103. setAnchor();
  2104. moveDown(qMax(count() - 1, 0));
  2105. m_movetype = MoveInclusive;
  2106. moveToEndOfLine();
  2107. setDotCommand(QString(QLatin1Char('D')));
  2108. finishMovement();
  2109. } else if ((input.is('D') || input.is('X')) &&
  2110. (isVisualCharMode() || isVisualLineMode())) {
  2111. leaveVisualMode();
  2112. m_rangemode = RangeLineMode;
  2113. m_submode = NoSubMode;
  2114. yankText(currentRange(), m_register);
  2115. removeText(currentRange());
  2116. moveToFirstNonBlankOnLine();
  2117. } else if ((input.is('D') || input.is('X')) && isVisualBlockMode()) {
  2118. leaveVisualMode();
  2119. m_rangemode = RangeBlockAndTailMode;
  2120. yankText(currentRange(), m_register);
  2121. removeText(currentRange());
  2122. setPosition(qMin(position(), anchor()));
  2123. } else if (input.isControl('d')) {
  2124. int sline = cursorLineOnScreen();
  2125. // FIXME: this should use the "scroll" option, and "count"
  2126. moveDown(linesOnScreen() / 2);
  2127. handleStartOfLine();
  2128. scrollToLine(cursorLine() - sline);
  2129. finishMovement();
  2130. } else if (input.is('e') || input.isShift(Key_Right)) {
  2131. m_movetype = MoveInclusive;
  2132. moveToWordBoundary(false, true);
  2133. setTargetColumn();
  2134. finishMovement("%1e", count());
  2135. } else if (input.is('E')) {
  2136. m_movetype = MoveInclusive;
  2137. moveToWordBoundary(true, true);
  2138. setTargetColumn();
  2139. finishMovement("%1E", count());
  2140. } else if (input.isControl('e')) {
  2141. // FIXME: this should use the "scroll" option, and "count"
  2142. if (cursorLineOnScreen() == 0)
  2143. moveDown(1);
  2144. scrollDown(1);
  2145. finishMovement();
  2146. } else if (input.is('f')) {
  2147. m_subsubmode = FtSubSubMode;
  2148. m_movetype = MoveInclusive;
  2149. m_subsubdata = input;
  2150. } else if (input.is('F')) {
  2151. m_subsubmode = FtSubSubMode;
  2152. m_movetype = MoveExclusive;
  2153. m_subsubdata = input;
  2154. } else if (input.is('g') && !m_gflag) {
  2155. m_gflag = true;
  2156. } else if (input.is('g') || input.is('G')) {
  2157. QString dotCommand = QString("%1G").arg(count());
  2158. if (input.is('G') && m_mvcount.isEmpty())
  2159. dotCommand = QString(QLatin1Char('G'));
  2160. if (input.is('g'))
  2161. m_gflag = false;
  2162. int n = (input.is('g')) ? 1 : linesInDocument();
  2163. n = m_mvcount.isEmpty() ? n : count();
  2164. if (m_submode == NoSubMode || m_submode == ZSubMode
  2165. || m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
  2166. setPosition(firstPositionInLine(n));
  2167. handleStartOfLine();
  2168. } else {
  2169. m_movetype = MoveLineWise;
  2170. m_rangemode = RangeLineMode;
  2171. setAnchor();
  2172. setPosition(firstPositionInLine(n));
  2173. }
  2174. finishMovement(dotCommand);
  2175. } else if (input.is('h') || input.isKey(Key_Left) || input.isBackspace()) {
  2176. m_movetype = MoveExclusive;
  2177. int n = qMin(count(), leftDist());
  2178. if (m_fakeEnd && block().length() > 1)
  2179. ++n;
  2180. moveLeft(n);
  2181. setTargetColumn();
  2182. finishMovement("%1h", count());
  2183. } else if (input.is('H')) {
  2184. setCursor(EDITOR(cursorForPosition(QPoint(0, 0))));
  2185. moveDown(qMax(count() - 1, 0));
  2186. handleStartOfLine();
  2187. finishMovement();
  2188. } else if (!isVisualMode() && (input.is('i') || input.isKey(Key_Insert))) {
  2189. setDotCommand(QString(QLatin1Char('i'))); // setDotCommand("%1i", count());
  2190. setUndoPosition();
  2191. breakEditBlock();
  2192. enterInsertMode();
  2193. updateMiniBuffer();
  2194. if (atEndOfLine())
  2195. moveLeft();
  2196. } else {
  2197. handled = handleCommandMode2(input);
  2198. }
  2199. return handled;
  2200. }
  2201. EventResult FakeVimHandler::Private::handleCommandMode2(const Input &input)
  2202. {
  2203. EventResult handled = EventHandled;
  2204. if (input.is('I')) {
  2205. setUndoPosition();
  2206. setDotCommand(QString(QLatin1Char('I'))); // setDotCommand("%1I", count());
  2207. if (isVisualMode()) {
  2208. int beginLine = lineForPosition(anchor());
  2209. int endLine = lineForPosition(position());
  2210. m_visualInsertCount = qAbs(endLine - beginLine);
  2211. setPosition(qMin(position(), anchor()));
  2212. } else {
  2213. if (m_gflag)
  2214. moveToStartOfLine();
  2215. else
  2216. moveToFirstNonBlankOnLine();
  2217. m_gflag = false;
  2218. //m_tc.clearSelection();
  2219. }
  2220. breakEditBlock();
  2221. enterInsertMode();
  2222. } else if (input.isControl('i')) {
  2223. if (!m_jumpListRedo.isEmpty()) {
  2224. m_jumpListUndo.append(cursorPosition());
  2225. setCursorPosition(m_jumpListRedo.last());
  2226. m_jumpListRedo.pop_back();
  2227. }
  2228. } else if (input.is('j') || input.isKey(Key_Down)
  2229. || input.isControl('j') || input.isControl('n')) {
  2230. m_movetype = MoveLineWise;
  2231. moveDown(count());
  2232. finishMovement("%1j", count());
  2233. } else if (input.is('J')) {
  2234. setDotCommand("%1J", count());
  2235. beginEditBlock();
  2236. if (m_submode == NoSubMode) {
  2237. for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
  2238. moveBehindEndOfLine();
  2239. setAnchor();
  2240. moveRight();
  2241. if (m_gflag) {
  2242. removeText(currentRange());
  2243. } else {
  2244. while (characterAtCursor() == ' '
  2245. || characterAtCursor() == '\t')
  2246. moveRight();
  2247. removeText(currentRange());
  2248. cursor().insertText(QString(QLatin1Char(' ')));
  2249. }
  2250. }
  2251. if (!m_gflag)
  2252. moveLeft();
  2253. }
  2254. endEditBlock();
  2255. finishMovement();
  2256. } else if (input.is('k') || input.isKey(Key_Up) || input.isControl('p')) {
  2257. m_movetype = MoveLineWise;
  2258. moveUp(count());
  2259. finishMovement("%1k", count());
  2260. } else if (input.is('l') || input.isKey(Key_Right) || input.is(' ')) {
  2261. m_movetype = MoveExclusive;
  2262. bool pastEnd = count() >= rightDist() - 1;
  2263. moveRight(qMax(0, qMin(count(), rightDist() - (m_submode == NoSubMode))));
  2264. setTargetColumn();
  2265. if (pastEnd && isVisualMode())
  2266. m_visualTargetColumn = -1;
  2267. finishMovement("%1l", count());
  2268. } else if (input.is('L')) {
  2269. QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
  2270. setCursor(tc);
  2271. moveUp(qMax(count(), 1));
  2272. handleStartOfLine();
  2273. finishMovement();
  2274. } else if (input.isControl('l')) {
  2275. // screen redraw. should not be needed
  2276. } else if (input.is('m')) {
  2277. if (m_gflag) {
  2278. moveToStartOfLine();
  2279. moveRight(qMin(columnsOnScreen() / 2, rightDist()) - 1);
  2280. setTargetColumn();
  2281. finishMovement();
  2282. } else {
  2283. m_subsubmode = MarkSubSubMode;
  2284. }
  2285. } else if (input.is('M')) {
  2286. QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
  2287. setCursor(tc);
  2288. handleStartOfLine();
  2289. finishMovement();
  2290. } else if (input.is('n') || input.is('N')) {
  2291. if (hasConfig(ConfigUseCoreSearch)) {
  2292. bool forward = (input.is('n')) ? m_lastSearchForward : !m_lastSearchForward;
  2293. int pos = position();
  2294. emit q->findNextRequested(!forward);
  2295. if (forward && pos == cursor().selectionStart()) {
  2296. // if cursor is already positioned at the start of a find result, this is returned
  2297. emit q->findNextRequested(false);
  2298. }
  2299. setPosition(cursor().selectionStart());
  2300. } else {
  2301. SearchData sd;
  2302. sd.needle = g.searchHistory.current();
  2303. sd.forward = input.is('n') ? m_lastSearchForward : !m_lastSearchForward;
  2304. sd.highlightCursor = false;
  2305. sd.highlightMatches = true;
  2306. search(sd);
  2307. }
  2308. } else if (isVisualMode() && (input.is('o') || input.is('O'))) {
  2309. int pos = position();
  2310. setAnchorAndPosition(pos, anchor());
  2311. std::swap(m_marks['<'], m_marks['>']);
  2312. std::swap(m_positionPastEnd, m_anchorPastEnd);
  2313. setTargetColumn();
  2314. if (m_positionPastEnd)
  2315. m_visualTargetColumn = -1;
  2316. updateSelection();
  2317. } else if (input.is('o')) {
  2318. setDotCommand("%1o", count());
  2319. setUndoPosition();
  2320. breakEditBlock();
  2321. enterInsertMode();
  2322. beginEditBlock();
  2323. moveToFirstNonBlankOnLine();
  2324. moveBehindEndOfLine();
  2325. insertText(QString("\n"));
  2326. insertAutomaticIndentation(true);
  2327. endEditBlock();
  2328. } else if (input.is('O')) {
  2329. setDotCommand("%1O", count());
  2330. setUndoPosition();
  2331. breakEditBlock();
  2332. enterInsertMode();
  2333. beginEditBlock();
  2334. moveToFirstNonBlankOnLine();
  2335. moveToStartOfLine();
  2336. insertText(QString("\n"));
  2337. moveUp();
  2338. insertAutomaticIndentation(false);
  2339. endEditBlock();
  2340. } else if (input.isControl('o')) {
  2341. if (!m_jumpListUndo.isEmpty()) {
  2342. m_jumpListRedo.append(cursorPosition());
  2343. setCursorPosition(m_jumpListUndo.last());
  2344. m_jumpListUndo.pop_back();
  2345. }
  2346. } else if (input.is('p') || input.is('P')) {
  2347. pasteText(input.is('p'));
  2348. setTargetColumn();
  2349. setDotCommand("%1p", count());
  2350. finishMovement();
  2351. } else if (input.is('r')) {
  2352. setUndoPosition();
  2353. m_submode = ReplaceSubMode;
  2354. } else if (!isVisualMode() && input.is('R')) {
  2355. setUndoPosition();
  2356. breakEditBlock();
  2357. enterReplaceMode();
  2358. updateMiniBuffer();
  2359. } else if (input.isControl('r')) {
  2360. redo();
  2361. } else if (input.is('s') && isVisualBlockMode()) {
  2362. setUndoPosition();
  2363. Range range(position(), anchor(), RangeBlockMode);
  2364. int beginLine = lineForPosition(anchor());
  2365. int endLine = lineForPosition(position());
  2366. m_visualInsertCount = qAbs(endLine - beginLine);
  2367. setPosition(qMin(position(), anchor()));
  2368. yankText(range, m_register);
  2369. removeText(range);
  2370. setDotCommand("%1s", count());
  2371. breakEditBlock();
  2372. enterInsertMode();
  2373. } else if (input.is('s')) {
  2374. setUndoPosition();
  2375. leaveVisualMode();
  2376. if (atEndOfLine())
  2377. moveLeft();
  2378. setAnchor();
  2379. moveRight(qMin(count(), rightDist()));
  2380. yankText(currentRange(), m_register);
  2381. removeText(currentRange());
  2382. setDotCommand("%1s", count());
  2383. m_opcount.clear();
  2384. m_mvcount.clear();
  2385. breakEditBlock();
  2386. enterInsertMode();
  2387. } else if (input.is('S')) {
  2388. setUndoPosition();
  2389. beginEditBlock();
  2390. if (!isVisualMode()) {
  2391. const int line = cursorLine() + 1;
  2392. const int anc = firstPositionInLine(line);
  2393. const int pos = lastPositionInLine(line + count() - 1);
  2394. setAnchorAndPosition(anc, pos);
  2395. }
  2396. setDotCommand("%1S", count());
  2397. breakEditBlock();
  2398. enterInsertMode();
  2399. m_submode = ChangeSubMode;
  2400. m_movetype = MoveLineWise;
  2401. endEditBlock();
  2402. finishMovement();
  2403. } else if (m_gflag && input.is('t')) {
  2404. m_gflag = false;
  2405. handleExCommand("tabnext");
  2406. } else if (input.is('t')) {
  2407. m_movetype = MoveInclusive;
  2408. m_subsubmode = FtSubSubMode;
  2409. m_subsubdata = input;
  2410. } else if (m_gflag && input.is('T')) {
  2411. m_gflag = false;
  2412. handleExCommand("tabprev");
  2413. } else if (input.is('T')) {
  2414. m_movetype = MoveExclusive;
  2415. m_subsubmode = FtSubSubMode;
  2416. m_subsubdata = input;
  2417. } else if (input.isControl('t')) {
  2418. handleExCommand("pop");
  2419. } else if (!m_gflag && input.is('u')) {
  2420. undo();
  2421. } else if (input.isControl('u')) {
  2422. int sline = cursorLineOnScreen();
  2423. // FIXME: this should use the "scroll" option, and "count"
  2424. moveUp(linesOnScreen() / 2);
  2425. handleStartOfLine();
  2426. scrollToLine(cursorLine() - sline);
  2427. finishMovement();
  2428. } else if (input.is('v')) {
  2429. toggleVisualMode(VisualCharMode);
  2430. } else if (input.is('V')) {
  2431. toggleVisualMode(VisualLineMode);
  2432. } else if (input.isControl('v')) {
  2433. toggleVisualMode(VisualBlockMode);
  2434. } else if (input.is('w')) { // tested
  2435. // Special case: "cw" and "cW" work the same as "ce" and "cE" if the
  2436. // cursor is on a non-blank - except if the cursor is on the last
  2437. // character of a word: only the current word will be changed
  2438. if (m_submode == ChangeSubMode) {
  2439. moveToWordBoundary(false, true, true);
  2440. setTargetColumn();
  2441. m_movetype = MoveInclusive;
  2442. } else {
  2443. moveToNextWord(false, m_submode == DeleteSubMode);
  2444. m_movetype = MoveExclusive;
  2445. }
  2446. finishMovement("%1w", count());
  2447. } else if (input.is('W')) {
  2448. if (m_submode == ChangeSubMode) {
  2449. moveToWordBoundary(true, true, true);
  2450. setTargetColumn();
  2451. m_movetype = MoveInclusive;
  2452. } else {
  2453. moveToNextWord(true, m_submode == DeleteSubMode);
  2454. m_movetype = MoveExclusive;
  2455. }
  2456. finishMovement("%1W", count());
  2457. } else if (input.isControl('w')) {
  2458. m_submode = WindowSubMode;
  2459. } else if (input.is('x') && isNoVisualMode()) { // = "dl"
  2460. m_movetype = MoveExclusive;
  2461. m_submode = DeleteSubMode;
  2462. const int n = qMin(count(), rightDist());
  2463. setAnchorAndPosition(position(), position() + n);
  2464. setDotCommand("%1x", count());
  2465. finishMovement();
  2466. } else if (input.isControl('x')) {
  2467. changeNumberTextObject(false);
  2468. } else if (input.is('X')) {
  2469. if (leftDist() > 0) {
  2470. setAnchor();
  2471. moveLeft(qMin(count(), leftDist()));
  2472. yankText(currentRange(), m_register);
  2473. removeText(currentRange());
  2474. }
  2475. finishMovement();
  2476. } else if ((m_submode == YankSubMode && input.is('y'))
  2477. || (input.is('Y') && isNoVisualMode())) {
  2478. setAnchor();
  2479. if (count() > 1)
  2480. moveDown(count()-1);
  2481. m_rangemode = RangeLineMode;
  2482. m_movetype = MoveLineWise;
  2483. m_submode = YankSubMode;
  2484. finishMovement();
  2485. } else if (input.isControl('y')) {
  2486. // FIXME: this should use the "scroll" option, and "count"
  2487. if (cursorLineOnScreen() == linesOnScreen() - 1)
  2488. moveUp(1);
  2489. scrollUp(1);
  2490. finishMovement();
  2491. } else if (input.is('y') && isNoVisualMode()) {
  2492. setAnchor();
  2493. m_submode = YankSubMode;
  2494. } else if (input.is('y') && isVisualCharMode()) {
  2495. Range range(position(), anchor(), RangeCharMode);
  2496. range.endPos++; // MoveInclusive
  2497. yankText(range, m_register);
  2498. setPosition(qMin(position(), anchor()));
  2499. leaveVisualMode();
  2500. finishMovement();
  2501. } else if ((input.is('y') && isVisualLineMode())
  2502. || (input.is('Y') && isVisualLineMode())
  2503. || (input.is('Y') && isVisualCharMode())) {
  2504. m_rangemode = RangeLineMode;
  2505. yankText(currentRange(), m_register);
  2506. setPosition(qMin(position(), anchor()));
  2507. moveToStartOfLine();
  2508. leaveVisualMode();
  2509. finishMovement();
  2510. } else if ((input.is('y') || input.is('Y')) && isVisualBlockMode()) {
  2511. m_rangemode = RangeBlockMode;
  2512. yankText(currentRange(), m_register);
  2513. setPosition(qMin(position(), anchor()));
  2514. leaveVisualMode();
  2515. finishMovement();
  2516. } else if (input.is('z')) {
  2517. m_submode = ZSubMode;
  2518. } else if (input.is('Z')) {
  2519. m_submode = CapitalZSubMode;
  2520. } else if (!m_gflag && input.is('~') && !isVisualMode()) {
  2521. m_movetype = MoveExclusive;
  2522. if (!atEndOfLine()) {
  2523. beginEditBlock();
  2524. setAnchor();
  2525. moveRight(qMin(count(), rightDist()));
  2526. if (input.is('~')) {
  2527. invertCase(currentRange());
  2528. setDotCommand("%1~", count());
  2529. } else if (input.is('u')) {
  2530. downCase(currentRange());
  2531. setDotCommand("%1gu", count());
  2532. } else if (input.is('U')) {
  2533. upCase(currentRange());
  2534. setDotCommand("%1gU", count());
  2535. }
  2536. endEditBlock();
  2537. }
  2538. finishMovement();
  2539. } else if ((m_gflag && input.is('~') && !isVisualMode())
  2540. || (m_gflag && input.is('u') && !isVisualMode())
  2541. || (m_gflag && input.is('U') && !isVisualMode())) {
  2542. m_gflag = false;
  2543. m_movetype = MoveExclusive;
  2544. if (atEndOfLine())
  2545. moveLeft();
  2546. setAnchor();
  2547. m_submode = TransformSubMode;
  2548. if (input.is('~'))
  2549. m_subsubmode = InvertCaseSubSubMode;
  2550. if (input.is('u'))
  2551. m_subsubmode = DownCaseSubSubMode;
  2552. else if (input.is('U'))
  2553. m_subsubmode = UpCaseSubSubMode;
  2554. } else if ((input.is('~') && isVisualMode())
  2555. || (m_gflag && input.is('u') && isVisualMode())
  2556. || (m_gflag && input.is('U') && isVisualMode())) {
  2557. m_gflag = false;
  2558. m_movetype = MoveExclusive;
  2559. if (isVisualLineMode())
  2560. m_rangemode = RangeLineMode;
  2561. else if (isVisualBlockMode())
  2562. m_rangemode = RangeBlockMode;
  2563. leaveVisualMode();
  2564. m_submode = TransformSubMode;
  2565. if (input.is('~'))
  2566. m_subsubmode = InvertCaseSubSubMode;
  2567. else if (input.is('u'))
  2568. m_subsubmode = DownCaseSubSubMode;
  2569. else if (input.is('U'))
  2570. m_subsubmode = UpCaseSubSubMode;
  2571. finishMovement();
  2572. } else if (input.is('[')) {
  2573. m_submode = OpenSquareSubMode;
  2574. } else if (input.is(']')) {
  2575. m_submode = CloseSquareSubMode;
  2576. } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
  2577. moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
  2578. scrollToLine(cursorLine());
  2579. handleStartOfLine();
  2580. finishMovement();
  2581. } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
  2582. moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
  2583. scrollToLine(cursorLine() + linesOnScreen() - 2);
  2584. handleStartOfLine();
  2585. finishMovement();
  2586. } else if (input.isKey(Key_Delete)) {
  2587. setAnchor();
  2588. moveRight(qMin(1, rightDist()));
  2589. removeText(currentRange());
  2590. if (atEndOfLine())
  2591. moveLeft();
  2592. } else if (input.isKey(Key_BracketLeft) || input.isKey(Key_BracketRight)) {
  2593. } else if (input.isControl(Key_BracketRight)) {
  2594. handleExCommand("tag");
  2595. } else {
  2596. //qDebug() << "IGNORED IN COMMAND MODE: " << key << text
  2597. // << " VISUAL: " << m_visualMode;
  2598. // if a key which produces text was pressed, don't mark it as unhandled
  2599. // - otherwise the text would be inserted while being in command mode
  2600. if (input.text().isEmpty()) {
  2601. handled = EventUnhandled;
  2602. }
  2603. }
  2604. m_positionPastEnd = (m_visualTargetColumn == -1) && isVisualMode();
  2605. return handled;
  2606. }
  2607. EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
  2608. {
  2609. if (input.isEscape()) {
  2610. moveLeft(qMin(1, leftDist()));
  2611. setTargetColumn();
  2612. m_submode = NoSubMode;
  2613. m_mode = CommandMode;
  2614. finishMovement();
  2615. } else if (input.isKey(Key_Left)) {
  2616. breakEditBlock();
  2617. moveLeft(1);
  2618. setTargetColumn();
  2619. } else if (input.isKey(Key_Right)) {
  2620. breakEditBlock();
  2621. moveRight(1);
  2622. setTargetColumn();
  2623. } else if (input.isKey(Key_Up)) {
  2624. breakEditBlock();
  2625. moveUp(1);
  2626. setTargetColumn();
  2627. } else if (input.isKey(Key_Down)) {
  2628. breakEditBlock();
  2629. moveDown(1);
  2630. } else {
  2631. joinPreviousEditBlock();
  2632. if (!atEndOfLine()) {
  2633. setAnchor();
  2634. moveRight();
  2635. m_lastDeletion += selectText(Range(position(), anchor()));
  2636. removeText(currentRange());
  2637. }
  2638. const QString text = input.text();
  2639. m_lastInsertion += text;
  2640. setAnchor();
  2641. insertText(text);
  2642. endEditBlock();
  2643. setTargetColumn();
  2644. }
  2645. return EventHandled;
  2646. }
  2647. EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
  2648. {
  2649. //const int key = input.key;
  2650. //const QString &text = input.text;
  2651. if (input.isEscape()) {
  2652. if (isVisualBlockMode() && !m_lastInsertion.contains('\n')) {
  2653. leaveVisualMode();
  2654. joinPreviousEditBlock();
  2655. moveLeft(m_lastInsertion.size());
  2656. setAnchor();
  2657. int pos = position();
  2658. setTargetColumn();
  2659. for (int i = 0; i < m_visualInsertCount; ++i) {
  2660. moveDown();
  2661. insertText(m_lastInsertion);
  2662. }
  2663. moveLeft(1);
  2664. Range range(pos, position(), RangeBlockMode);
  2665. yankText(range);
  2666. setPosition(pos);
  2667. setDotCommand("p");
  2668. endEditBlock();
  2669. } else {
  2670. // Normal insertion. Start with '1', as one instance was
  2671. // already physically inserted while typing.
  2672. QString data;
  2673. for (int i = 1; i < count(); ++i)
  2674. data += m_lastInsertion;
  2675. insertText(data);
  2676. moveLeft(qMin(1, leftDist()));
  2677. setTargetColumn();
  2678. leaveVisualMode();
  2679. breakEditBlock();
  2680. }
  2681. g.dotCommand += m_lastInsertion;
  2682. g.dotCommand += QChar(27);
  2683. enterCommandMode();
  2684. m_submode = NoSubMode;
  2685. m_ctrlVActive = false;
  2686. m_opcount.clear();
  2687. m_mvcount.clear();
  2688. } else if (m_ctrlVActive) {
  2689. insertInInsertMode(input.raw());
  2690. } else if (input.isControl('v')) {
  2691. m_ctrlVActive = true;
  2692. } else if (input.isControl('w')) {
  2693. int endPos = position();
  2694. moveToWordBoundary(false, false, false);
  2695. setTargetColumn();
  2696. int beginPos = position();
  2697. Range range(beginPos, endPos, RangeCharMode);
  2698. removeText(range);
  2699. } else if (input.isKey(Key_Insert)) {
  2700. if (m_mode == ReplaceMode)
  2701. m_mode = InsertMode;
  2702. else
  2703. m_mode = ReplaceMode;
  2704. } else if (input.isKey(Key_Left)) {
  2705. moveLeft(count());
  2706. setTargetColumn();
  2707. m_lastInsertion.clear();
  2708. } else if (input.isControl(Key_Left)) {
  2709. moveToWordBoundary(false, false);
  2710. setTargetColumn();
  2711. m_lastInsertion.clear();
  2712. } else if (input.isKey(Key_Down)) {
  2713. //removeAutomaticIndentation();
  2714. m_submode = NoSubMode;
  2715. moveDown(count());
  2716. m_lastInsertion.clear();
  2717. } else if (input.isKey(Key_Up)) {
  2718. //removeAutomaticIndentation();
  2719. m_submode = NoSubMode;
  2720. moveUp(count());
  2721. m_lastInsertion.clear();
  2722. } else if (input.isKey(Key_Right)) {
  2723. moveRight(count());
  2724. setTargetColumn();
  2725. m_lastInsertion.clear();
  2726. } else if (input.isControl(Key_Right)) {
  2727. moveToWordBoundary(false, true);
  2728. moveRight(); // we need one more move since we are in insert mode
  2729. setTargetColumn();
  2730. m_lastInsertion.clear();
  2731. } else if (input.isKey(Key_Home)) {
  2732. moveToStartOfLine();
  2733. setTargetColumn();
  2734. m_lastInsertion.clear();
  2735. } else if (input.isKey(Key_End)) {
  2736. if (count() > 1)
  2737. moveDown(count() - 1);
  2738. moveBehindEndOfLine();
  2739. setTargetColumn();
  2740. m_lastInsertion.clear();
  2741. } else if (input.isReturn()) {
  2742. joinPreviousEditBlock();
  2743. m_submode = NoSubMode;
  2744. insertText(QString("\n"));
  2745. m_lastInsertion += '\n';
  2746. insertAutomaticIndentation(true);
  2747. setTargetColumn();
  2748. endEditBlock();
  2749. } else if (input.isBackspace()) {
  2750. joinPreviousEditBlock();
  2751. m_justAutoIndented = 0;
  2752. if (!m_lastInsertion.isEmpty()
  2753. || hasConfig(ConfigBackspace, "start")
  2754. || hasConfig(ConfigBackspace, "2")) {
  2755. const int line = cursorLine() + 1;
  2756. const Column col = cursorColumn();
  2757. QString data = lineContents(line);
  2758. const Column ind = indentation(data);
  2759. if (col.logical <= ind.logical && col.logical
  2760. && startsWithWhitespace(data, col.physical)) {
  2761. const int ts = config(ConfigTabStop).toInt();
  2762. const int newl = col.logical - 1 - (col.logical - 1) % ts;
  2763. const QString prefix = tabExpand(newl);
  2764. setLineContents(line, prefix + data.mid(col.physical));
  2765. moveToStartOfLine();
  2766. moveRight(prefix.size());
  2767. m_lastInsertion.clear(); // FIXME
  2768. } else {
  2769. setAnchor();
  2770. cursor().deletePreviousChar();
  2771. m_lastInsertion.chop(1);
  2772. }
  2773. setTargetColumn();
  2774. }
  2775. endEditBlock();
  2776. } else if (input.isKey(Key_Delete)) {
  2777. setAnchor();
  2778. cursor().deleteChar();
  2779. m_lastInsertion.clear();
  2780. } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
  2781. removeAutomaticIndentation();
  2782. moveDown(count() * (linesOnScreen() - 2));
  2783. m_lastInsertion.clear();
  2784. } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
  2785. removeAutomaticIndentation();
  2786. moveUp(count() * (linesOnScreen() - 2));
  2787. m_lastInsertion.clear();
  2788. } else if (input.isKey(Key_Tab)) {
  2789. m_justAutoIndented = 0;
  2790. if (hasConfig(ConfigExpandTab)) {
  2791. const int ts = config(ConfigTabStop).toInt();
  2792. const int col = logicalCursorColumn();
  2793. QString str = QString(ts - col % ts, ' ');
  2794. m_lastInsertion.append(str);
  2795. insertText(str);
  2796. setTargetColumn();
  2797. } else {
  2798. insertInInsertMode(input.raw());
  2799. }
  2800. } else if (input.isControl('d')) {
  2801. // remove one level of indentation from the current line
  2802. int shift = config(ConfigShiftWidth).toInt();
  2803. int tab = config(ConfigTabStop).toInt();
  2804. int line = cursorLine() + 1;
  2805. int pos = firstPositionInLine(line);
  2806. QString text = lineContents(line);
  2807. int amount = 0;
  2808. int i = 0;
  2809. for (; i < text.size() && amount < shift; ++i) {
  2810. if (text.at(i) == ' ')
  2811. ++amount;
  2812. else if (text.at(i) == '\t')
  2813. amount += tab; // FIXME: take position into consideration
  2814. else
  2815. break;
  2816. }
  2817. removeText(Range(pos, pos+i));
  2818. //} else if (key >= control('a') && key <= control('z')) {
  2819. // // ignore these
  2820. } else if (input.isControl('p') || input.isControl('n')) {
  2821. QTextCursor tc = EDITOR(textCursor());
  2822. moveToWordBoundary(false, false);
  2823. QString str = selectText(Range(position(), tc.position()));
  2824. EDITOR(setTextCursor(tc));
  2825. emit q->simpleCompletionRequested(str, input.isControl('n'));
  2826. } else if (!input.text().isEmpty()) {
  2827. insertInInsertMode(input.text());
  2828. } else {
  2829. // We don't want fancy stuff in insert mode.
  2830. return EventHandled;
  2831. }
  2832. updateMiniBuffer();
  2833. return EventHandled;
  2834. }
  2835. void FakeVimHandler::Private::insertInInsertMode(const QString &text)
  2836. {
  2837. joinPreviousEditBlock();
  2838. m_justAutoIndented = 0;
  2839. m_lastInsertion.append(text);
  2840. insertText(text);
  2841. if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
  2842. const QString leftText = block().text()
  2843. .left(position() - 1 - block().position());
  2844. if (leftText.simplified().isEmpty()) {
  2845. Range range(position(), position(), m_rangemode);
  2846. indentText(range, text.at(0));
  2847. }
  2848. }
  2849. setTargetColumn();
  2850. endEditBlock();
  2851. m_ctrlVActive = false;
  2852. }
  2853. EventResult FakeVimHandler::Private::handleExMode(const Input &input)
  2854. {
  2855. if (input.isEscape()) {
  2856. m_commandBuffer.clear();
  2857. enterCommandMode();
  2858. updateMiniBuffer();
  2859. m_ctrlVActive = false;
  2860. } else if (m_ctrlVActive) {
  2861. m_commandBuffer.insertChar(input.raw());
  2862. m_ctrlVActive = false;
  2863. } else if (input.isControl('v')) {
  2864. m_ctrlVActive = true;
  2865. } else if (input.isBackspace()) {
  2866. if (m_commandBuffer.isEmpty()) {
  2867. m_commandPrefix.clear();
  2868. enterCommandMode();
  2869. } else {
  2870. m_commandBuffer.deleteChar();
  2871. }
  2872. updateMiniBuffer();
  2873. } else if (input.isKey(Key_Tab)) {
  2874. QStringList completions;
  2875. foreach (const QString &entry, g.commandHistory.items()) {
  2876. if (entry.startsWith(m_commandBuffer.contents()))
  2877. completions.append(entry);
  2878. }
  2879. qDebug() << completions;
  2880. } else if (input.isKey(Key_Left)) {
  2881. m_commandBuffer.moveLeft();
  2882. updateMiniBuffer();
  2883. } else if (input.isReturn()) {
  2884. if (!m_commandBuffer.isEmpty()) {
  2885. //g.commandHistory.takeLast();
  2886. g.commandHistory.append(m_commandBuffer.contents());
  2887. handleExCommand(m_commandBuffer.contents());
  2888. if (m_textedit || m_plaintextedit)
  2889. leaveVisualMode();
  2890. }
  2891. updateMiniBuffer();
  2892. } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
  2893. g.commandHistory.up();
  2894. m_commandBuffer.setContents(g.commandHistory.current());
  2895. updateMiniBuffer();
  2896. } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
  2897. g.commandHistory.down();
  2898. m_commandBuffer.setContents(g.commandHistory.current());
  2899. updateMiniBuffer();
  2900. } else if (m_commandBuffer.handleInput(input)) {
  2901. updateMiniBuffer();
  2902. } else {
  2903. qDebug() << "IGNORED IN EX-MODE: " << input.key() << input.text();
  2904. return EventUnhandled;
  2905. }
  2906. return EventHandled;
  2907. }
  2908. EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
  2909. {
  2910. if (input.isEscape()) {
  2911. m_commandBuffer.clear();
  2912. g.searchHistory.append(m_searchCursor.selectedText());
  2913. m_searchCursor = QTextCursor();
  2914. updateSelection();
  2915. enterCommandMode();
  2916. updateMiniBuffer();
  2917. } else if (input.isBackspace()) {
  2918. if (m_commandBuffer.isEmpty()) {
  2919. m_commandPrefix.clear();
  2920. m_searchCursor = QTextCursor();
  2921. enterCommandMode();
  2922. } else {
  2923. m_commandBuffer.deleteChar();
  2924. }
  2925. updateMiniBuffer();
  2926. } else if (input.isKey(Key_Left)) {
  2927. m_commandBuffer.moveLeft();
  2928. updateMiniBuffer();
  2929. } else if (input.isKey(Key_Right)) {
  2930. m_commandBuffer.moveRight();
  2931. updateMiniBuffer();
  2932. } else if (input.isReturn()) {
  2933. m_searchCursor = QTextCursor();
  2934. QString needle = m_commandBuffer.contents();
  2935. if (!needle.isEmpty()) {
  2936. g.searchHistory.append(needle);
  2937. if (!hasConfig(ConfigIncSearch)) {
  2938. SearchData sd;
  2939. sd.needle = needle;
  2940. sd.forward = m_lastSearchForward;
  2941. sd.highlightCursor = false;
  2942. sd.highlightMatches = true;
  2943. search(sd);
  2944. }
  2945. finishMovement(m_commandPrefix + needle + '\n');
  2946. }
  2947. enterCommandMode();
  2948. highlightMatches(needle);
  2949. updateMiniBuffer();
  2950. } else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
  2951. // FIXME: This and the three cases below are wrong as vim
  2952. // takes only matching entries in the history into account.
  2953. g.searchHistory.up();
  2954. showBlackMessage(g.searchHistory.current());
  2955. } else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
  2956. g.searchHistory.down();
  2957. showBlackMessage(g.searchHistory.current());
  2958. } else if (input.isKey(Key_Tab)) {
  2959. m_commandBuffer.insertChar(QChar(9));
  2960. updateMiniBuffer();
  2961. } else if (m_commandBuffer.handleInput(input)) {
  2962. updateMiniBuffer();
  2963. }
  2964. if (hasConfig(ConfigIncSearch) && !input.isReturn() && !input.isEscape()) {
  2965. SearchData sd;
  2966. sd.needle = m_commandBuffer.contents();
  2967. sd.forward = m_lastSearchForward;
  2968. sd.mustMove = false;
  2969. sd.highlightCursor = true;
  2970. sd.highlightMatches = false;
  2971. search(sd);
  2972. }
  2973. //else {
  2974. // qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
  2975. // return EventUnhandled;
  2976. //}
  2977. return EventHandled;
  2978. }
  2979. // This uses 1 based line counting.
  2980. int FakeVimHandler::Private::readLineCode(QString &cmd)
  2981. {
  2982. //qDebug() << "CMD: " << cmd;
  2983. if (cmd.isEmpty())
  2984. return -1;
  2985. QChar c = cmd.at(0);
  2986. cmd = cmd.mid(1);
  2987. if (c == '.') {
  2988. if (cmd.isEmpty())
  2989. return cursorLine() + 1;
  2990. QChar c1 = cmd.at(0);
  2991. if (c1 == '+' || c1 == '-') {
  2992. // Repeat for things like .+4
  2993. cmd = cmd.mid(1);
  2994. return cursorLine() + readLineCode(cmd);
  2995. }
  2996. return cursorLine() + 1;
  2997. }
  2998. if (c == '$')
  2999. return linesInDocument();
  3000. if (c == '\'' && !cmd.isEmpty()) {
  3001. if (cmd.isEmpty()) {
  3002. showRedMessage(msgMarkNotSet(QString()));
  3003. return -1;
  3004. }
  3005. int m = mark(cmd.at(0).unicode());
  3006. if (m == -1) {
  3007. showRedMessage(msgMarkNotSet(cmd.at(0)));
  3008. cmd = cmd.mid(1);
  3009. return -1;
  3010. }
  3011. cmd = cmd.mid(1);
  3012. return lineForPosition(m);
  3013. }
  3014. if (c == '-') {
  3015. int n = readLineCode(cmd);
  3016. return cursorLine() + 1 - (n == -1 ? 1 : n);
  3017. }
  3018. if (c == '+') {
  3019. int n = readLineCode(cmd);
  3020. return cursorLine() + 1 + (n == -1 ? 1 : n);
  3021. }
  3022. if (c == '\'' && !cmd.isEmpty()) {
  3023. int pos = mark(cmd.at(0).unicode());
  3024. if (pos == -1) {
  3025. showRedMessage(msgMarkNotSet(cmd.at(0)));
  3026. cmd = cmd.mid(1);
  3027. return -1;
  3028. }
  3029. cmd = cmd.mid(1);
  3030. return lineForPosition(pos);
  3031. }
  3032. if (c.isDigit()) {
  3033. int n = c.unicode() - '0';
  3034. while (!cmd.isEmpty()) {
  3035. c = cmd.at(0);
  3036. if (!c.isDigit())
  3037. break;
  3038. cmd = cmd.mid(1);
  3039. n = n * 10 + (c.unicode() - '0');
  3040. }
  3041. //qDebug() << "N: " << n;
  3042. return n;
  3043. }
  3044. // Parsing failed.
  3045. cmd = c + cmd;
  3046. return -1;
  3047. }
  3048. void FakeVimHandler::Private::setCurrentRange(const Range &range)
  3049. {
  3050. setAnchorAndPosition(range.beginPos, range.endPos);
  3051. m_rangemode = range.rangemode;
  3052. }
  3053. Range FakeVimHandler::Private::rangeFromCurrentLine() const
  3054. {
  3055. Range range;
  3056. int line = cursorLine() + 1;
  3057. range.beginPos = firstPositionInLine(line);
  3058. range.endPos = lastPositionInLine(line);
  3059. return range;
  3060. }
  3061. // use handleExCommand for invoking commands that might move the cursor
  3062. void FakeVimHandler::Private::handleCommand(const QString &cmd)
  3063. {
  3064. handleExCommand(cmd);
  3065. }
  3066. bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
  3067. // :substitute
  3068. {
  3069. QString flags;
  3070. QRegExp pattern;
  3071. QString replacement;
  3072. int count = 0;
  3073. if (cmd.cmd.startsWith("&&")) {
  3074. flags = cmd.cmd.mid(2);
  3075. if (flags.isEmpty())
  3076. flags = m_lastSubstituteFlags;
  3077. pattern = m_lastSubstitutePattern;
  3078. replacement = m_lastSubstituteReplacement;
  3079. count = cmd.args.section(QLatin1Char(' '), 1, 1).toInt();
  3080. } else if (cmd.cmd.startsWith(QLatin1Char('&'))) {
  3081. flags = cmd.cmd.mid(1);
  3082. if (flags.isEmpty())
  3083. flags = m_lastSubstituteFlags;
  3084. pattern = m_lastSubstitutePattern;
  3085. replacement = m_lastSubstituteReplacement;
  3086. count = cmd.args.section(QLatin1Char(' '), 1, 1).toInt();
  3087. } else if (cmd.matches("s", "substitute")) {
  3088. flags = m_lastSubstituteFlags;
  3089. if (flags.isEmpty())
  3090. flags = m_lastSubstituteFlags;
  3091. pattern = m_lastSubstitutePattern;
  3092. replacement = m_lastSubstituteReplacement;
  3093. count = cmd.args.section(QLatin1Char(' '), 2, 2).toInt();
  3094. } else {
  3095. QString line = cmd.cmd + ' ' + cmd.args;
  3096. line = line.trimmed();
  3097. if (line.startsWith(_("substitute")))
  3098. line = line.mid(10);
  3099. else if (line.startsWith('s') && line.size() > 1
  3100. && !isalpha(line.at(1).unicode()))
  3101. line = line.mid(1);
  3102. else
  3103. return false;
  3104. // we have /{pattern}/{string}/[flags] now
  3105. if (line.isEmpty())
  3106. return false;
  3107. const QChar separator = line.at(0);
  3108. int pos1 = -1;
  3109. int pos2 = -1;
  3110. int i;
  3111. for (i = 1; i < line.size(); ++i) {
  3112. if (line.at(i) == separator && line.at(i - 1) != '\\') {
  3113. pos1 = i;
  3114. break;
  3115. }
  3116. }
  3117. if (pos1 == -1)
  3118. return false;
  3119. for (++i; i < line.size(); ++i) {
  3120. if (line.at(i) == separator && line.at(i - 1) != '\\') {
  3121. pos2 = i;
  3122. break;
  3123. }
  3124. }
  3125. if (pos2 == -1)
  3126. pos2 = line.size();
  3127. QString needle = line.mid(1, pos1 - 1);
  3128. replacement = line.mid(pos1 + 1, pos2 - pos1 - 1);
  3129. flags = line.mid(pos2 + 1);
  3130. needle.replace('$', '\n');
  3131. needle.replace("\\\n", "\\$");
  3132. pattern.setPattern(needle);
  3133. m_lastSubstituteFlags = flags;
  3134. m_lastSubstitutePattern = pattern;
  3135. m_lastSubstituteReplacement = replacement;
  3136. }
  3137. if (count == 0)
  3138. count = 1;
  3139. if (flags.contains('i'))
  3140. pattern.setCaseSensitivity(Qt::CaseInsensitive);
  3141. beginEditBlock();
  3142. const bool global = flags.contains('g');
  3143. for (int a = 0; a != count; ++a) {
  3144. const Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
  3145. const int beginLine = lineForPosition(range.beginPos);
  3146. const int endLine = lineForPosition(range.endPos);
  3147. for (int line = endLine; line >= beginLine; --line) {
  3148. QString origText = lineContents(line);
  3149. QString text = origText;
  3150. int pos = 0;
  3151. while (true) {
  3152. pos = pattern.indexIn(text, pos, QRegExp::CaretAtZero);
  3153. if (pos == -1)
  3154. break;
  3155. if (pattern.cap(0).isEmpty())
  3156. break;
  3157. QStringList caps = pattern.capturedTexts();
  3158. QString matched = text.mid(pos, caps.at(0).size());
  3159. QString repl = replacement;
  3160. for (int i = 1; i < caps.size(); ++i)
  3161. repl.replace("\\" + QString::number(i), caps.at(i));
  3162. for (int i = 0; i < repl.size(); ++i) {
  3163. if (repl.at(i) == '&' && (i == 0 || repl.at(i - 1) != '\\')) {
  3164. repl.replace(i, 1, caps.at(0));
  3165. i += caps.at(0).size();
  3166. }
  3167. }
  3168. repl.replace("\\&", "&");
  3169. text = text.left(pos) + repl + text.mid(pos + matched.size());
  3170. pos += repl.size();
  3171. if (!global)
  3172. break;
  3173. }
  3174. if (text != origText)
  3175. setLineContents(line, text);
  3176. }
  3177. }
  3178. moveToStartOfLine();
  3179. setTargetColumn();
  3180. endEditBlock();
  3181. return true;
  3182. }
  3183. bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map
  3184. {
  3185. QByteArray modes;
  3186. enum Type { Map, Noremap, Unmap } type;
  3187. QByteArray cmd = cmd0.cmd.toLatin1();
  3188. // Strange formatting. But everything else is even uglier.
  3189. if (cmd == "map") { modes = "nvo"; type = Map; } else
  3190. if (cmd == "nm" || cmd == "nmap") { modes = "n"; type = Map; } else
  3191. if (cmd == "vm" || cmd == "vmap") { modes = "v"; type = Map; } else
  3192. if (cmd == "xm" || cmd == "xmap") { modes = "x"; type = Map; } else
  3193. if (cmd == "smap") { modes = "s"; type = Map; } else
  3194. if (cmd == "map!") { modes = "ic"; type = Map; } else
  3195. if (cmd == "im" || cmd == "imap") { modes = "i"; type = Map; } else
  3196. if (cmd == "lm" || cmd == "lmap") { modes = "l"; type = Map; } else
  3197. if (cmd == "cm" || cmd == "cmap") { modes = "c"; type = Map; } else
  3198. if (cmd == "no" || cmd == "noremap") { modes = "nvo"; type = Noremap; } else
  3199. if (cmd == "nn" || cmd == "nnoremap") { modes = "n"; type = Noremap; } else
  3200. if (cmd == "vn" || cmd == "vnoremap") { modes = "v"; type = Noremap; } else
  3201. if (cmd == "xn" || cmd == "xnoremap") { modes = "x"; type = Noremap; } else
  3202. if (cmd == "snor" || cmd == "snoremap") { modes = "s"; type = Noremap; } else
  3203. if (cmd == "ono" || cmd == "onoremap") { modes = "o"; type = Noremap; } else
  3204. if (cmd == "no!" || cmd == "noremap!") { modes = "ic"; type = Noremap; } else
  3205. if (cmd == "ino" || cmd == "inoremap") { modes = "i"; type = Noremap; } else
  3206. if (cmd == "ln" || cmd == "lnoremap") { modes = "l"; type = Noremap; } else
  3207. if (cmd == "cno" || cmd == "cnoremap") { modes = "c"; type = Noremap; } else
  3208. if (cmd == "unm" || cmd == "unmap") { modes = "nvo"; type = Unmap; } else
  3209. if (cmd == "nun" || cmd == "nunmap") { modes = "n"; type = Unmap; } else
  3210. if (cmd == "vu" || cmd == "vunmap") { modes = "v"; type = Unmap; } else
  3211. if (cmd == "xu" || cmd == "xunmap") { modes = "x"; type = Unmap; } else
  3212. if (cmd == "sunm" || cmd == "sunmap") { modes = "s"; type = Unmap; } else
  3213. if (cmd == "ou" || cmd == "ounmap") { modes = "o"; type = Unmap; } else
  3214. if (cmd == "unm!" || cmd == "unmap!") { modes = "ic"; type = Unmap; } else
  3215. if (cmd == "iu" || cmd == "iunmap") { modes = "i"; type = Unmap; } else
  3216. if (cmd == "lu" || cmd == "lunmap") { modes = "l"; type = Unmap; } else
  3217. if (cmd == "cu" || cmd == "cunmap") { modes = "c"; type = Unmap; }
  3218. else
  3219. return false;
  3220. const int pos = cmd0.args.indexOf(QLatin1Char(' '));
  3221. if (pos == -1) {
  3222. // FIXME: Dump mappings here.
  3223. //qDebug() << g.mappings;
  3224. return true;
  3225. }
  3226. QString lhs = cmd0.args.left(pos);
  3227. QString rhs = cmd0.args.mid(pos + 1);
  3228. Inputs key;
  3229. key.parseFrom(lhs);
  3230. //qDebug() << "MAPPING: " << modes << lhs << rhs;
  3231. switch (type) {
  3232. case Unmap:
  3233. foreach (char c, modes)
  3234. if (g.mappings.contains(c))
  3235. g.mappings[c].remove(key);
  3236. break;
  3237. case Map:
  3238. rhs = rhs; // FIXME: expand rhs.
  3239. // Fall through.
  3240. case Noremap: {
  3241. Inputs inputs(rhs);
  3242. foreach (char c, modes)
  3243. g.mappings[c].insert(key, inputs);
  3244. break;
  3245. }
  3246. }
  3247. return true;
  3248. }
  3249. bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd)
  3250. {
  3251. // :his[tory]
  3252. if (!cmd.matches("his", "history"))
  3253. return false;
  3254. if (cmd.args.isEmpty()) {
  3255. QString info;
  3256. info += "# command history\n";
  3257. int i = 0;
  3258. foreach (const QString &item, g.commandHistory.items()) {
  3259. ++i;
  3260. info += QString("%1 %2\n").arg(i, -8).arg(item);
  3261. }
  3262. emit q->extraInformationChanged(info);
  3263. } else {
  3264. notImplementedYet();
  3265. }
  3266. updateMiniBuffer();
  3267. return true;
  3268. }
  3269. bool FakeVimHandler::Private::handleExRegisterCommand(const ExCommand &cmd)
  3270. {
  3271. // :reg[isters] and :di[splay]
  3272. if (!cmd.matches("reg", "registers") && !cmd.matches("di", "display"))
  3273. return false;
  3274. QByteArray regs = cmd.args.toLatin1();
  3275. if (regs.isEmpty()) {
  3276. regs = "\"0123456789";
  3277. QHashIterator<int, Register> it(g.registers);
  3278. while (it.hasNext()) {
  3279. it.next();
  3280. if (it.key() > '9')
  3281. regs += char(it.key());
  3282. }
  3283. }
  3284. QString info;
  3285. info += "--- Registers ---\n";
  3286. foreach (char reg, regs) {
  3287. QString value = quoteUnprintable(registerContents(reg));
  3288. info += QString("\"%1 %2\n").arg(reg).arg(value);
  3289. }
  3290. emit q->extraInformationChanged(info);
  3291. updateMiniBuffer();
  3292. return true;
  3293. }
  3294. bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd)
  3295. {
  3296. // :se[t]
  3297. if (!cmd.matches("se", "set"))
  3298. return false;
  3299. showBlackMessage(QString());
  3300. SavedAction *act = theFakeVimSettings()->item(cmd.args);
  3301. QTC_CHECK(!cmd.args.isEmpty()); // Handled by plugin.
  3302. if (act && act->value().type() == QVariant::Bool) {
  3303. // Boolean config to be switched on.
  3304. bool oldValue = act->value().toBool();
  3305. if (oldValue == false)
  3306. act->setValue(true);
  3307. else if (oldValue == true)
  3308. {} // nothing to do
  3309. } else if (act) {
  3310. // Non-boolean to show.
  3311. showBlackMessage(cmd.args + '=' + act->value().toString());
  3312. } else if (cmd.args.startsWith(_("no"))
  3313. && (act = theFakeVimSettings()->item(cmd.args.mid(2)))) {
  3314. // Boolean config to be switched off.
  3315. bool oldValue = act->value().toBool();
  3316. if (oldValue == true)
  3317. act->setValue(false);
  3318. else if (oldValue == false)
  3319. {} // nothing to do
  3320. } else if (cmd.args.contains('=')) {
  3321. // Non-boolean config to set.
  3322. int p = cmd.args.indexOf('=');
  3323. QString error = theFakeVimSettings()
  3324. ->trySetValue(cmd.args.left(p), cmd.args.mid(p + 1));
  3325. if (!error.isEmpty())
  3326. showRedMessage(error);
  3327. } else {
  3328. showRedMessage(FakeVimHandler::tr("Unknown option: ") + cmd.args);
  3329. }
  3330. updateMiniBuffer();
  3331. updateEditor();
  3332. return true;
  3333. }
  3334. bool FakeVimHandler::Private::handleExNormalCommand(const ExCommand &cmd)
  3335. {
  3336. // :norm[al]
  3337. if (!cmd.matches("norm", "normal"))
  3338. return false;
  3339. //qDebug() << "REPLAY NORMAL: " << quoteUnprintable(reNormal.cap(3));
  3340. replay(cmd.args, 1);
  3341. return true;
  3342. }
  3343. bool FakeVimHandler::Private::handleExDeleteCommand(const ExCommand &cmd)
  3344. {
  3345. // :d[elete]
  3346. if (!cmd.matches("d", "delete"))
  3347. return false;
  3348. Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
  3349. setCurrentRange(range);
  3350. QString reg = cmd.args;
  3351. QString text = selectText(range);
  3352. removeText(currentRange());
  3353. if (!reg.isEmpty()) {
  3354. const int r = reg.at(0).unicode();
  3355. setRegisterContents(r, text);
  3356. setRegisterRangeMode(r, RangeLineMode);
  3357. }
  3358. return true;
  3359. }
  3360. bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd)
  3361. {
  3362. // :w, :x, :wq, ...
  3363. //static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
  3364. if (cmd.cmd != "w" && cmd.cmd != "x" && cmd.cmd != "wq")
  3365. return false;
  3366. int beginLine = lineForPosition(cmd.range.beginPos);
  3367. int endLine = lineForPosition(cmd.range.endPos);
  3368. const bool noArgs = (beginLine == -1);
  3369. if (beginLine == -1)
  3370. beginLine = 0;
  3371. if (endLine == -1)
  3372. endLine = linesInDocument();
  3373. //qDebug() << "LINES: " << beginLine << endLine;
  3374. //QString prefix = cmd.args;
  3375. const bool forced = cmd.hasBang;
  3376. //const bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x'));
  3377. //const bool quitAll = quit && prefix.contains(QChar('a'));
  3378. QString fileName = cmd.args;
  3379. if (fileName.isEmpty())
  3380. fileName = m_currentFileName;
  3381. QFile file1(fileName);
  3382. const bool exists = file1.exists();
  3383. if (exists && !forced && !noArgs) {
  3384. showRedMessage(FakeVimHandler::tr
  3385. ("File \"%1\" exists (add ! to override)").arg(fileName));
  3386. } else if (file1.open(QIODevice::ReadWrite)) {
  3387. // Nobody cared, so act ourselves.
  3388. file1.close();
  3389. Range range(firstPositionInLine(beginLine),
  3390. firstPositionInLine(endLine), RangeLineMode);
  3391. QString contents = selectText(range);
  3392. QFile::remove(fileName);
  3393. QFile file2(fileName);
  3394. if (file2.open(QIODevice::ReadWrite)) {
  3395. QTextStream ts(&file2);
  3396. ts << contents;
  3397. } else {
  3398. showRedMessage(FakeVimHandler::tr
  3399. ("Cannot open file \"%1\" for writing").arg(fileName));
  3400. }
  3401. // Check result by reading back.
  3402. QFile file3(fileName);
  3403. file3.open(QIODevice::ReadOnly);
  3404. QByteArray ba = file3.readAll();
  3405. showBlackMessage(FakeVimHandler::tr("\"%1\" %2 %3L, %4C written")
  3406. .arg(fileName).arg(exists ? " " : tr(" [New] "))
  3407. .arg(ba.count('\n')).arg(ba.size()));
  3408. //if (quitAll)
  3409. // passUnknownExCommand(forced ? "qa!" : "qa");
  3410. //else if (quit)
  3411. // passUnknownExCommand(forced ? "q!" : "q");
  3412. } else {
  3413. showRedMessage(FakeVimHandler::tr
  3414. ("Cannot open file \"%1\" for reading").arg(fileName));
  3415. }
  3416. return true;
  3417. }
  3418. bool FakeVimHandler::Private::handleExReadCommand(const ExCommand &cmd)
  3419. {
  3420. // :r[ead]
  3421. if (!cmd.matches("r", "read"))
  3422. return false;
  3423. beginEditBlock();
  3424. moveToStartOfLine();
  3425. setTargetColumn();
  3426. moveDown();
  3427. m_currentFileName = cmd.args;
  3428. QFile file(m_currentFileName);
  3429. file.open(QIODevice::ReadOnly);
  3430. QTextStream ts(&file);
  3431. QString data = ts.readAll();
  3432. insertText(data);
  3433. endEditBlock();
  3434. showBlackMessage(FakeVimHandler::tr("\"%1\" %2L, %3C")
  3435. .arg(m_currentFileName).arg(data.count('\n')).arg(data.size()));
  3436. return true;
  3437. }
  3438. bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :!
  3439. {
  3440. if (!cmd.cmd.startsWith(QLatin1Char('!')))
  3441. return false;
  3442. setCurrentRange(cmd.range);
  3443. int targetPosition = firstPositionInLine(lineForPosition(cmd.range.beginPos));
  3444. QString command = QString(cmd.cmd.mid(1) + ' ' + cmd.args).trimmed();
  3445. QString text = selectText(cmd.range);
  3446. QProcess proc;
  3447. proc.start(command);
  3448. proc.waitForStarted();
  3449. #ifdef Q_OS_WIN
  3450. text.replace(_("\n"), _("\r\n"));
  3451. #endif
  3452. proc.write(text.toUtf8());
  3453. proc.closeWriteChannel();
  3454. proc.waitForFinished();
  3455. QString result = QString::fromUtf8(proc.readAllStandardOutput());
  3456. if (text.isEmpty()) {
  3457. emit q->extraInformationChanged(result);
  3458. } else {
  3459. beginEditBlock();
  3460. removeText(currentRange());
  3461. insertText(result);
  3462. setPosition(targetPosition);
  3463. endEditBlock();
  3464. leaveVisualMode();
  3465. //qDebug() << "FILTER: " << command;
  3466. showBlackMessage(FakeVimHandler::tr("%n lines filtered", 0,
  3467. text.count('\n')));
  3468. }
  3469. return true;
  3470. }
  3471. bool FakeVimHandler::Private::handleExShiftCommand(const ExCommand &cmd)
  3472. {
  3473. if (cmd.cmd != "<" && cmd.cmd != ">")
  3474. return false;
  3475. Range range = cmd.range;
  3476. if (cmd.range.endPos == 0)
  3477. range = rangeFromCurrentLine();
  3478. setCurrentRange(range);
  3479. int count = qMax(1, cmd.args.toInt());
  3480. if (cmd.cmd == "<")
  3481. shiftRegionLeft(count);
  3482. else
  3483. shiftRegionRight(count);
  3484. leaveVisualMode();
  3485. const int beginLine = lineForPosition(range.beginPos);
  3486. const int endLine = lineForPosition(range.endPos);
  3487. showBlackMessage(FakeVimHandler::tr("%n lines %1ed %2 time", 0,
  3488. (endLine - beginLine + 1)).arg(cmd.cmd).arg(count));
  3489. return true;
  3490. }
  3491. bool FakeVimHandler::Private::handleExNohlsearchCommand(const ExCommand &cmd)
  3492. {
  3493. // :nohlsearch
  3494. if (!cmd.cmd.startsWith("noh"))
  3495. return false;
  3496. m_searchSelections.clear();
  3497. updateSelection();
  3498. return true;
  3499. }
  3500. bool FakeVimHandler::Private::handleExRedoCommand(const ExCommand &cmd)
  3501. {
  3502. // :redo
  3503. if (cmd.cmd != "red" && cmd.cmd != "redo")
  3504. return false;
  3505. redo();
  3506. updateMiniBuffer();
  3507. return true;
  3508. }
  3509. bool FakeVimHandler::Private::handleExGotoCommand(const ExCommand &cmd)
  3510. {
  3511. // :<nr>
  3512. if (!cmd.cmd.isEmpty())
  3513. return false;
  3514. const int beginLine = lineForPosition(cmd.range.beginPos);
  3515. setPosition(firstPositionInLine(beginLine));
  3516. showBlackMessage(QString());
  3517. return true;
  3518. }
  3519. bool FakeVimHandler::Private::handleExSourceCommand(const ExCommand &cmd)
  3520. {
  3521. // :source
  3522. if (cmd.cmd != "so" && cmd.cmd != "source")
  3523. return false;
  3524. QString fileName = cmd.args;
  3525. QFile file(fileName);
  3526. if (!file.open(QIODevice::ReadOnly)) {
  3527. showRedMessage(FakeVimHandler::tr("Cannot open file %1").arg(fileName));
  3528. return true;
  3529. }
  3530. bool inFunction = false;
  3531. while (!file.atEnd()) {
  3532. QByteArray line = file.readLine();
  3533. line = line.trimmed();
  3534. if (line.startsWith("function")) {
  3535. //qDebug() << "IGNORING FUNCTION" << line;
  3536. inFunction = true;
  3537. } else if (inFunction && line.startsWith("endfunction")) {
  3538. inFunction = false;
  3539. } else if (line.startsWith("function")) {
  3540. //qDebug() << "IGNORING FUNCTION" << line;
  3541. inFunction = true;
  3542. } else if (line.startsWith('"')) {
  3543. // A comment.
  3544. } else if (!line.isEmpty() && !inFunction) {
  3545. //qDebug() << "EXECUTING: " << line;
  3546. ExCommand cmd;
  3547. cmd.setContentsFromLine(QString::fromLocal8Bit(line));
  3548. handleExCommandHelper(cmd);
  3549. }
  3550. }
  3551. file.close();
  3552. return true;
  3553. }
  3554. bool FakeVimHandler::Private::handleExEchoCommand(const ExCommand &cmd)
  3555. {
  3556. // :echo
  3557. if (cmd.cmd != "echo")
  3558. return false;
  3559. m_currentMessage = cmd.args;
  3560. return true;
  3561. }
  3562. void FakeVimHandler::Private::handleExCommand(const QString &line0)
  3563. {
  3564. QString line = line0; // Make sure we have a copy to prevent aliasing.
  3565. if (line.endsWith(QLatin1Char('%'))) {
  3566. line.chop(1);
  3567. int percent = line.toInt();
  3568. setPosition(firstPositionInLine(percent * linesInDocument() / 100));
  3569. showBlackMessage(QString());
  3570. return;
  3571. }
  3572. // FIXME: that seems to be different for %w and %s
  3573. if (line.startsWith(QLatin1Char('%')))
  3574. line = "1,$" + line.mid(1);
  3575. const int beginLine = readLineCode(line);
  3576. int endLine = -1;
  3577. if (line.startsWith(',')) {
  3578. line = line.mid(1);
  3579. endLine = readLineCode(line);
  3580. }
  3581. if (beginLine != -1 && endLine == -1)
  3582. endLine = beginLine;
  3583. ExCommand cmd;
  3584. cmd.setContentsFromLine(line);
  3585. if (beginLine != -1) {
  3586. const int beginPos = firstPositionInLine(beginLine);
  3587. const int endPos = lastPositionInLine(endLine);
  3588. cmd.range = Range(beginPos, endPos, RangeLineMode);
  3589. cmd.count = beginLine;
  3590. }
  3591. //qDebug() << "CMD: " << cmd;
  3592. enterCommandMode();
  3593. showBlackMessage(QString());
  3594. if (!handleExCommandHelper(cmd))
  3595. showRedMessage(tr("Not an editor command: %1").arg(cmd.cmd));
  3596. }
  3597. bool FakeVimHandler::Private::handleExCommandHelper(const ExCommand &cmd)
  3598. {
  3599. return handleExPluginCommand(cmd)
  3600. || handleExGotoCommand(cmd)
  3601. || handleExBangCommand(cmd)
  3602. || handleExHistoryCommand(cmd)
  3603. || handleExRegisterCommand(cmd)
  3604. || handleExDeleteCommand(cmd)
  3605. || handleExMapCommand(cmd)
  3606. || handleExNohlsearchCommand(cmd)
  3607. || handleExNormalCommand(cmd)
  3608. || handleExReadCommand(cmd)
  3609. || handleExRedoCommand(cmd)
  3610. || handleExSetCommand(cmd)
  3611. || handleExShiftCommand(cmd)
  3612. || handleExSourceCommand(cmd)
  3613. || handleExSubstituteCommand(cmd)
  3614. || handleExWriteCommand(cmd)
  3615. || handleExEchoCommand(cmd);
  3616. }
  3617. bool FakeVimHandler::Private::handleExPluginCommand(const ExCommand &cmd)
  3618. {
  3619. bool handled = false;
  3620. emit q->handleExCommandRequested(&handled, cmd);
  3621. //qDebug() << "HANDLER REQUEST: " << cmd.cmd << handled;
  3622. return handled;
  3623. }
  3624. static QRegExp vimPatternToQtPattern(QString needle, QTextDocument::FindFlags *flags)
  3625. {
  3626. // FIXME: Rough mapping of a common case.
  3627. if (needle.startsWith(_("\\<")) && needle.endsWith(_("\\>")))
  3628. (*flags) |= QTextDocument::FindWholeWords;
  3629. // Half-hearted attempt at removing pitfalls.
  3630. if (needle.startsWith(_(".*")))
  3631. needle = needle.mid(2);
  3632. if (needle.endsWith(_(".*")))
  3633. needle = needle.left(needle.size() - 2);
  3634. needle.remove(_("\\<")); // start of word
  3635. needle.remove(_("\\>")); // end of word
  3636. // QRegExp's | and \| have the opposite meaning of vim's.
  3637. QString dummy(QLatin1Char(1));
  3638. needle.replace(_("\\|"), dummy);
  3639. needle.replace(_("|"), _("\\|"));
  3640. needle.replace(dummy, _("|"));
  3641. //qDebug() << "NEEDLE " << needle << needle;
  3642. return QRegExp(needle);
  3643. }
  3644. void FakeVimHandler::Private::searchBalanced(bool forward, QChar needle, QChar other)
  3645. {
  3646. int level = 1;
  3647. int pos = position();
  3648. const int npos = forward ? lastPositionInDocument() : 0;
  3649. while (true) {
  3650. if (forward)
  3651. ++pos;
  3652. else
  3653. --pos;
  3654. if (pos == npos)
  3655. return;
  3656. QChar c = document()->characterAt(pos);
  3657. if (c == other)
  3658. ++level;
  3659. else if (c == needle)
  3660. --level;
  3661. if (level == 0) {
  3662. const int oldLine = cursorLine() - cursorLineOnScreen();
  3663. // Making this unconditional feels better, but is not "vim like".
  3664. if (oldLine != cursorLine() - cursorLineOnScreen())
  3665. scrollToLine(cursorLine() - linesOnScreen() / 2);
  3666. setPosition(pos);
  3667. setTargetColumn();
  3668. updateSelection();
  3669. recordJump();
  3670. return;
  3671. }
  3672. }
  3673. }
  3674. void FakeVimHandler::Private::search(const SearchData &sd)
  3675. {
  3676. if (sd.needle.isEmpty())
  3677. return;
  3678. const bool incSearch = hasConfig(ConfigIncSearch);
  3679. QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
  3680. if (!sd.forward)
  3681. flags |= QTextDocument::FindBackward;
  3682. QRegExp needleExp = vimPatternToQtPattern(sd.needle, &flags);
  3683. const int oldLine = cursorLine() - cursorLineOnScreen();
  3684. int startPos = position();
  3685. if (sd.mustMove)
  3686. sd.forward ? ++startPos : --startPos;
  3687. m_searchCursor = QTextCursor();
  3688. QTextCursor tc = document()->find(needleExp, startPos, flags);
  3689. if (tc.isNull()) {
  3690. int startPos = sd.forward ? 0 : lastPositionInDocument();
  3691. tc = document()->find(needleExp, startPos, flags);
  3692. if (tc.isNull()) {
  3693. if (!incSearch) {
  3694. highlightMatches(QString());
  3695. showRedMessage(FakeVimHandler::tr("Pattern not found: %1")
  3696. .arg(needleExp.pattern()));
  3697. }
  3698. updateSelection();
  3699. return;
  3700. }
  3701. if (!incSearch) {
  3702. QString msg = sd.forward
  3703. ? FakeVimHandler::tr("search hit BOTTOM, continuing at TOP")
  3704. : FakeVimHandler::tr("search hit TOP, continuing at BOTTOM");
  3705. showRedMessage(msg);
  3706. }
  3707. }
  3708. // Set Cursor. In contrast to the main editor we have the cursor
  3709. // position before the anchor position.
  3710. setAnchorAndPosition(tc.position(), tc.anchor());
  3711. // Making this unconditional feels better, but is not "vim like".
  3712. if (oldLine != cursorLine() - cursorLineOnScreen())
  3713. scrollToLine(cursorLine() - linesOnScreen() / 2);
  3714. if (incSearch && sd.highlightCursor)
  3715. m_searchCursor = cursor();
  3716. setTargetColumn();
  3717. if (sd.highlightMatches)
  3718. highlightMatches(sd.needle);
  3719. updateSelection();
  3720. recordJump();
  3721. }
  3722. void FakeVimHandler::Private::highlightMatches(const QString &needle)
  3723. {
  3724. if (!hasConfig(ConfigHlSearch))
  3725. return;
  3726. if (needle == m_oldNeedle)
  3727. return;
  3728. m_oldNeedle = needle;
  3729. m_searchSelections.clear();
  3730. if (!needle.isEmpty()) {
  3731. QTextCursor tc = cursor();
  3732. tc.movePosition(StartOfDocument, MoveAnchor);
  3733. QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
  3734. QRegExp needleExp = vimPatternToQtPattern(needle, &flags);
  3735. while (!tc.atEnd()) {
  3736. tc = tc.document()->find(needleExp, tc.position(), flags);
  3737. if (tc.isNull())
  3738. break;
  3739. QTextEdit::ExtraSelection sel;
  3740. sel.cursor = tc;
  3741. sel.format = tc.blockCharFormat();
  3742. sel.format.setBackground(QColor(177, 177, 0));
  3743. m_searchSelections.append(sel);
  3744. if (document()->characterAt(tc.position()) == ParagraphSeparator)
  3745. tc.movePosition(Right, MoveAnchor);
  3746. }
  3747. }
  3748. updateSelection();
  3749. }
  3750. void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
  3751. {
  3752. QTextDocument *doc = document();
  3753. int firstPos = block().position();
  3754. for (int i = firstPos, n = firstPos + block().length(); i < n; ++i) {
  3755. if (!doc->characterAt(i).isSpace() || i == n - 1) {
  3756. setPosition(i);
  3757. return;
  3758. }
  3759. }
  3760. setPosition(block().position());
  3761. }
  3762. void FakeVimHandler::Private::indentSelectedText(QChar typedChar)
  3763. {
  3764. beginEditBlock();
  3765. setTargetColumn();
  3766. int beginLine = qMin(lineForPosition(position()), lineForPosition(anchor()));
  3767. int endLine = qMax(lineForPosition(position()), lineForPosition(anchor()));
  3768. Range range(anchor(), position(), m_rangemode);
  3769. indentText(range, typedChar);
  3770. setPosition(firstPositionInLine(beginLine));
  3771. handleStartOfLine();
  3772. setTargetColumn();
  3773. setDotCommand("%1==", endLine - beginLine + 1);
  3774. endEditBlock();
  3775. }
  3776. void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar)
  3777. {
  3778. int beginLine = lineForPosition(range.beginPos);
  3779. int endLine = lineForPosition(range.endPos);
  3780. if (beginLine > endLine)
  3781. qSwap(beginLine, endLine);
  3782. // LineForPosition has returned 1-based line numbers.
  3783. emit q->indentRegion(beginLine - 1, endLine - 1, typedChar);
  3784. if (beginLine != endLine)
  3785. showBlackMessage("MARKS ARE OFF NOW");
  3786. }
  3787. bool FakeVimHandler::Private::isElectricCharacter(QChar c) const
  3788. {
  3789. bool result = false;
  3790. emit q->checkForElectricCharacter(&result, c);
  3791. return result;
  3792. }
  3793. void FakeVimHandler::Private::shiftRegionRight(int repeat)
  3794. {
  3795. int beginLine = lineForPosition(anchor());
  3796. int endLine = lineForPosition(position());
  3797. int targetPos = anchor();
  3798. if (beginLine > endLine) {
  3799. qSwap(beginLine, endLine);
  3800. targetPos = position();
  3801. }
  3802. if (hasConfig(ConfigStartOfLine))
  3803. targetPos = firstPositionInLine(beginLine);
  3804. const int sw = config(ConfigShiftWidth).toInt();
  3805. beginEditBlock();
  3806. for (int line = beginLine; line <= endLine; ++line) {
  3807. QString data = lineContents(line);
  3808. const Column col = indentation(data);
  3809. data = tabExpand(col.logical + sw * repeat) + data.mid(col.physical);
  3810. setLineContents(line, data);
  3811. }
  3812. endEditBlock();
  3813. setPosition(targetPos);
  3814. handleStartOfLine();
  3815. setTargetColumn();
  3816. setDotCommand("%1>>", endLine - beginLine + 1);
  3817. }
  3818. void FakeVimHandler::Private::shiftRegionLeft(int repeat)
  3819. {
  3820. int beginLine = lineForPosition(anchor());
  3821. int endLine = lineForPosition(position());
  3822. int targetPos = anchor();
  3823. if (beginLine > endLine) {
  3824. qSwap(beginLine, endLine);
  3825. targetPos = position();
  3826. }
  3827. const int shift = config(ConfigShiftWidth).toInt() * repeat;
  3828. const int tab = config(ConfigTabStop).toInt();
  3829. if (hasConfig(ConfigStartOfLine))
  3830. targetPos = firstPositionInLine(beginLine);
  3831. beginEditBlock();
  3832. for (int line = endLine; line >= beginLine; --line) {
  3833. int pos = firstPositionInLine(line);
  3834. const QString text = lineContents(line);
  3835. int amount = 0;
  3836. int i = 0;
  3837. for (; i < text.size() && amount < shift; ++i) {
  3838. if (text.at(i) == ' ')
  3839. amount++;
  3840. else if (text.at(i) == '\t')
  3841. amount += tab; // FIXME: take position into consideration
  3842. else
  3843. break;
  3844. }
  3845. removeText(Range(pos, pos + i));
  3846. setPosition(pos);
  3847. }
  3848. endEditBlock();
  3849. setPosition(targetPos);
  3850. handleStartOfLine();
  3851. setTargetColumn();
  3852. setDotCommand("%1<<", endLine - beginLine + 1);
  3853. }
  3854. void FakeVimHandler::Private::moveToTargetColumn()
  3855. {
  3856. const QTextBlock &bl = block();
  3857. //Column column = cursorColumn();
  3858. //int logical = logical
  3859. const int maxcol = bl.length() - 2;
  3860. if (m_targetColumn == -1) {
  3861. setPosition(bl.position() + qMax(0, maxcol));
  3862. return;
  3863. }
  3864. const int physical = logicalToPhysicalColumn(m_targetColumn, bl.text());
  3865. //qDebug() << "CORRECTING COLUMN FROM: " << logical << "TO" << m_targetColumn;
  3866. if (physical >= maxcol)
  3867. setPosition(bl.position() + qMax(0, maxcol));
  3868. else
  3869. setPosition(bl.position() + physical);
  3870. }
  3871. /* if simple is given:
  3872. * class 0: spaces
  3873. * class 1: non-spaces
  3874. * else
  3875. * class 0: spaces
  3876. * class 1: non-space-or-letter-or-number
  3877. * class 2: letter-or-number
  3878. */
  3879. int FakeVimHandler::Private::charClass(QChar c, bool simple) const
  3880. {
  3881. if (simple)
  3882. return c.isSpace() ? 0 : 1;
  3883. // FIXME: This means that only characters < 256 in the
  3884. // ConfigIsKeyword setting are handled properly.
  3885. if (c.unicode() < 256) {
  3886. //int old = (c.isLetterOrNumber() || c.unicode() == '_') ? 2
  3887. // : c.isSpace() ? 0 : 1;
  3888. //qDebug() << c.unicode() << old << m_charClass[c.unicode()];
  3889. return m_charClass[c.unicode()];
  3890. }
  3891. if (c.isLetterOrNumber() || c.unicode() == '_')
  3892. return 2;
  3893. return c.isSpace() ? 0 : 1;
  3894. }
  3895. // Helper to parse a-z,A-Z,48-57,_
  3896. static int someInt(const QString &str)
  3897. {
  3898. if (str.toInt())
  3899. return str.toInt();
  3900. if (str.size())
  3901. return str.at(0).unicode();
  3902. return 0;
  3903. }
  3904. void FakeVimHandler::Private::setupCharClass()
  3905. {
  3906. for (int i = 0; i < 256; ++i) {
  3907. const QChar c = QChar(QLatin1Char(i));
  3908. m_charClass[i] = c.isSpace() ? 0 : 1;
  3909. }
  3910. const QString conf = config(ConfigIsKeyword).toString();
  3911. foreach (const QString &part, conf.split(QLatin1Char(','))) {
  3912. if (part.contains(QLatin1Char('-'))) {
  3913. const int from = someInt(part.section(QLatin1Char('-'), 0, 0));
  3914. const int to = someInt(part.section(QLatin1Char('-'), 1, 1));
  3915. for (int i = qMax(0, from); i <= qMin(255, to); ++i)
  3916. m_charClass[i] = 2;
  3917. } else {
  3918. m_charClass[qMin(255, someInt(part))] = 2;
  3919. }
  3920. }
  3921. }
  3922. void FakeVimHandler::Private::moveToWordBoundary(bool simple, bool forward, bool changeWord)
  3923. {
  3924. int repeat = count();
  3925. QTextDocument *doc = document();
  3926. int n = forward ? lastPositionInDocument() : 0;
  3927. int lastClass = -1;
  3928. if (changeWord) {
  3929. lastClass = charClass(characterAtCursor(), simple);
  3930. --repeat;
  3931. if (changeWord && block().length() == 1) // empty line
  3932. --repeat;
  3933. }
  3934. while (repeat >= 0) {
  3935. QChar c = doc->characterAt(position() + (forward ? 1 : -1));
  3936. int thisClass = charClass(c, simple);
  3937. if (thisClass != lastClass && (lastClass != 0 || changeWord))
  3938. --repeat;
  3939. if (repeat == -1)
  3940. break;
  3941. lastClass = thisClass;
  3942. if (position() == n)
  3943. break;
  3944. forward ? moveRight() : moveLeft();
  3945. if (changeWord && block().length() == 1) // empty line
  3946. --repeat;
  3947. if (repeat == -1)
  3948. break;
  3949. }
  3950. }
  3951. bool FakeVimHandler::Private::handleFfTt(QString key)
  3952. {
  3953. int key0 = key.size() == 1 ? key.at(0).unicode() : 0;
  3954. int oldPos = position();
  3955. // m_subsubmode \in { 'f', 'F', 't', 'T' }
  3956. bool forward = m_subsubdata.is('f') || m_subsubdata.is('t');
  3957. int repeat = count();
  3958. QTextDocument *doc = document();
  3959. int n = block().position();
  3960. if (forward)
  3961. n += block().length();
  3962. int pos = position();
  3963. while (pos != n) {
  3964. pos += forward ? 1 : -1;
  3965. if (pos == n)
  3966. break;
  3967. int uc = doc->characterAt(pos).unicode();
  3968. if (uc == ParagraphSeparator)
  3969. break;
  3970. if (uc == key0)
  3971. --repeat;
  3972. if (repeat == 0) {
  3973. if (m_subsubdata.is('t'))
  3974. --pos;
  3975. else if (m_subsubdata.is('T'))
  3976. ++pos;
  3977. if (forward)
  3978. moveRight(pos - position());
  3979. else
  3980. moveLeft(position() - pos);
  3981. break;
  3982. }
  3983. }
  3984. if (repeat == 0) {
  3985. setTargetColumn();
  3986. return true;
  3987. }
  3988. setPosition(oldPos);
  3989. return false;
  3990. }
  3991. void FakeVimHandler::Private::moveToNextWord(bool simple, bool deleteWord)
  3992. {
  3993. int repeat = count();
  3994. int n = lastPositionInDocument();
  3995. int lastClass = charClass(characterAtCursor(), simple);
  3996. while (true) {
  3997. QChar c = characterAtCursor();
  3998. int thisClass = charClass(c, simple);
  3999. if (thisClass != lastClass && thisClass != 0)
  4000. --repeat;
  4001. if (repeat == 0)
  4002. break;
  4003. lastClass = thisClass;
  4004. moveRight();
  4005. if (deleteWord) {
  4006. if (atBlockEnd())
  4007. --repeat;
  4008. } else {
  4009. if (block().length() == 1) // empty line
  4010. --repeat;
  4011. }
  4012. if (repeat == 0)
  4013. break;
  4014. if (position() == n)
  4015. break;
  4016. }
  4017. setTargetColumn();
  4018. }
  4019. void FakeVimHandler::Private::moveToMatchingParanthesis()
  4020. {
  4021. bool moved = false;
  4022. bool forward = false;
  4023. const int anc = anchor();
  4024. QTextCursor tc = cursor();
  4025. emit q->moveToMatchingParenthesis(&moved, &forward, &tc);
  4026. if (moved && forward)
  4027. tc.movePosition(Left, KeepAnchor, 1);
  4028. setAnchorAndPosition(anc, tc.position());
  4029. setTargetColumn();
  4030. }
  4031. int FakeVimHandler::Private::cursorLineOnScreen() const
  4032. {
  4033. if (!editor())
  4034. return 0;
  4035. QRect rect = EDITOR(cursorRect());
  4036. return rect.y() / rect.height();
  4037. }
  4038. int FakeVimHandler::Private::linesOnScreen() const
  4039. {
  4040. if (!editor())
  4041. return 1;
  4042. QRect rect = EDITOR(cursorRect());
  4043. return EDITOR(height()) / rect.height();
  4044. }
  4045. int FakeVimHandler::Private::columnsOnScreen() const
  4046. {
  4047. if (!editor())
  4048. return 1;
  4049. QRect rect = EDITOR(cursorRect());
  4050. // qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
  4051. return EDITOR(width()) / rect.width();
  4052. }
  4053. int FakeVimHandler::Private::cursorLine() const
  4054. {
  4055. return block().blockNumber();
  4056. }
  4057. int FakeVimHandler::Private::physicalCursorColumn() const
  4058. {
  4059. return position() - block().position();
  4060. }
  4061. int FakeVimHandler::Private::physicalToLogicalColumn
  4062. (const int physical, const QString &line) const
  4063. {
  4064. const int ts = config(ConfigTabStop).toInt();
  4065. int p = 0;
  4066. int logical = 0;
  4067. while (p < physical) {
  4068. QChar c = line.at(p);
  4069. //if (c == QLatin1Char(' '))
  4070. // ++logical;
  4071. //else
  4072. if (c == QLatin1Char('\t'))
  4073. logical += ts - logical % ts;
  4074. else
  4075. ++logical;
  4076. //break;
  4077. ++p;
  4078. }
  4079. return logical;
  4080. }
  4081. int FakeVimHandler::Private::logicalToPhysicalColumn
  4082. (const int logical, const QString &line) const
  4083. {
  4084. const int ts = config(ConfigTabStop).toInt();
  4085. int physical = 0;
  4086. for (int l = 0; l < logical && physical < line.size(); ++physical) {
  4087. QChar c = line.at(physical);
  4088. if (c == QLatin1Char('\t'))
  4089. l += ts - l % ts;
  4090. else
  4091. ++l;
  4092. }
  4093. return physical;
  4094. }
  4095. int FakeVimHandler::Private::logicalCursorColumn() const
  4096. {
  4097. const int physical = physicalCursorColumn();
  4098. const QString line = block().text();
  4099. return physicalToLogicalColumn(physical, line);
  4100. }
  4101. Column FakeVimHandler::Private::cursorColumn() const
  4102. {
  4103. return Column(physicalCursorColumn(), logicalCursorColumn());
  4104. }
  4105. int FakeVimHandler::Private::linesInDocument() const
  4106. {
  4107. if (cursor().isNull())
  4108. return 0;
  4109. const int count = document()->blockCount();
  4110. // Qt inserts an empty line if the last character is a '\n',
  4111. // but that's not how vi does it.
  4112. return document()->lastBlock().length() <= 1 ? count - 1 : count;
  4113. }
  4114. void FakeVimHandler::Private::scrollToLine(int line)
  4115. {
  4116. // FIXME: works only for QPlainTextEdit
  4117. QScrollBar *scrollBar = EDITOR(verticalScrollBar());
  4118. //qDebug() << "SCROLL: " << scrollBar->value() << line;
  4119. scrollBar->setValue(line);
  4120. //QTC_CHECK(firstVisibleLine() == line);
  4121. }
  4122. int FakeVimHandler::Private::firstVisibleLine() const
  4123. {
  4124. QScrollBar *scrollBar = EDITOR(verticalScrollBar());
  4125. if (0 && scrollBar->value() != cursorLine() - cursorLineOnScreen()) {
  4126. qDebug() << "SCROLLBAR: " << scrollBar->value()
  4127. << "CURSORLINE IN DOC" << cursorLine()
  4128. << "CURSORLINE ON SCREEN" << cursorLineOnScreen();
  4129. }
  4130. //return scrollBar->value();
  4131. return cursorLine() - cursorLineOnScreen();
  4132. }
  4133. void FakeVimHandler::Private::scrollUp(int count)
  4134. {
  4135. scrollToLine(cursorLine() - cursorLineOnScreen() - count);
  4136. }
  4137. int FakeVimHandler::Private::lastPositionInDocument() const
  4138. {
  4139. QTextBlock block = document()->lastBlock();
  4140. return block.position() + block.length() - 1;
  4141. }
  4142. QString FakeVimHandler::Private::selectText(const Range &range) const
  4143. {
  4144. if (range.rangemode == RangeCharMode) {
  4145. QTextCursor tc = cursor();
  4146. tc.setPosition(range.beginPos, MoveAnchor);
  4147. tc.setPosition(range.endPos, KeepAnchor);
  4148. return tc.selection().toPlainText();
  4149. }
  4150. if (range.rangemode == RangeLineMode) {
  4151. QTextCursor tc = cursor();
  4152. int firstPos = firstPositionInLine(lineForPosition(range.beginPos));
  4153. int lastLine = lineForPosition(range.endPos);
  4154. bool endOfDoc = lastLine == document()->lastBlock().blockNumber() + 1;
  4155. int lastPos = endOfDoc ? lastPositionInDocument() : firstPositionInLine(lastLine + 1);
  4156. tc.setPosition(firstPos, MoveAnchor);
  4157. tc.setPosition(lastPos, KeepAnchor);
  4158. return tc.selection().toPlainText() + QString((endOfDoc? "\n" : ""));
  4159. }
  4160. // FIXME: Performance?
  4161. int beginLine = lineForPosition(range.beginPos);
  4162. int endLine = lineForPosition(range.endPos);
  4163. int beginColumn = 0;
  4164. int endColumn = INT_MAX;
  4165. if (range.rangemode == RangeBlockMode) {
  4166. int column1 = range.beginPos - firstPositionInLine(beginLine);
  4167. int column2 = range.endPos - firstPositionInLine(endLine);
  4168. beginColumn = qMin(column1, column2);
  4169. endColumn = qMax(column1, column2);
  4170. }
  4171. int len = endColumn - beginColumn + 1;
  4172. QString contents;
  4173. QTextBlock block = document()->findBlockByNumber(beginLine - 1);
  4174. for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
  4175. QString line = block.text();
  4176. if (range.rangemode == RangeBlockMode) {
  4177. line = line.mid(beginColumn, len);
  4178. if (line.size() < len)
  4179. line += QString(len - line.size(), QChar(' '));
  4180. }
  4181. contents += line;
  4182. if (!contents.endsWith('\n'))
  4183. contents += '\n';
  4184. block = block.next();
  4185. }
  4186. //qDebug() << "SELECTED: " << contents;
  4187. return contents;
  4188. }
  4189. void FakeVimHandler::Private::yankText(const Range &range, int reg)
  4190. {
  4191. setRegisterContents(reg, selectText(range));
  4192. setRegisterRangeMode(reg, range.rangemode);
  4193. }
  4194. void FakeVimHandler::Private::transformText(const Range &range,
  4195. Transformation transformFunc, const QVariant &extra)
  4196. {
  4197. QTextCursor tc = cursor();
  4198. switch (range.rangemode) {
  4199. case RangeCharMode: {
  4200. // This can span multiple lines.
  4201. beginEditBlock();
  4202. tc.setPosition(range.beginPos, MoveAnchor);
  4203. tc.setPosition(range.endPos, KeepAnchor);
  4204. TransformationData td(tc.selectedText(), extra);
  4205. (this->*transformFunc)(&td);
  4206. tc.removeSelectedText();
  4207. tc.insertText(td.to);
  4208. endEditBlock();
  4209. return;
  4210. }
  4211. case RangeLineMode:
  4212. case RangeLineModeExclusive: {
  4213. beginEditBlock();
  4214. tc.setPosition(range.beginPos, MoveAnchor);
  4215. tc.movePosition(StartOfLine, MoveAnchor);
  4216. tc.setPosition(range.endPos, KeepAnchor);
  4217. tc.movePosition(EndOfLine, KeepAnchor);
  4218. if (range.rangemode != RangeLineModeExclusive) {
  4219. // make sure that complete lines are removed
  4220. // - also at the beginning and at the end of the document
  4221. if (tc.atEnd()) {
  4222. tc.setPosition(range.beginPos, MoveAnchor);
  4223. tc.movePosition(StartOfLine, MoveAnchor);
  4224. if (!tc.atStart()) {
  4225. // also remove first line if it is the only one
  4226. tc.movePosition(Left, MoveAnchor, 1);
  4227. tc.movePosition(EndOfLine, MoveAnchor, 1);
  4228. }
  4229. tc.setPosition(range.endPos, KeepAnchor);
  4230. tc.movePosition(EndOfLine, KeepAnchor);
  4231. } else {
  4232. tc.movePosition(Right, KeepAnchor, 1);
  4233. }
  4234. }
  4235. TransformationData td(tc.selectedText(), extra);
  4236. (this->*transformFunc)(&td);
  4237. tc.removeSelectedText();
  4238. tc.insertText(td.to);
  4239. endEditBlock();
  4240. return;
  4241. }
  4242. case RangeBlockAndTailMode:
  4243. case RangeBlockMode: {
  4244. int beginLine = lineForPosition(range.beginPos);
  4245. int endLine = lineForPosition(range.endPos);
  4246. int column1 = range.beginPos - firstPositionInLine(beginLine);
  4247. int column2 = range.endPos - firstPositionInLine(endLine);
  4248. int beginColumn = qMin(column1, column2);
  4249. int endColumn = qMax(column1, column2);
  4250. if (range.rangemode == RangeBlockAndTailMode)
  4251. endColumn = INT_MAX - 1;
  4252. QTextBlock block = document()->findBlockByNumber(endLine - 1);
  4253. beginEditBlock();
  4254. for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
  4255. int bCol = qMin(beginColumn, block.length() - 1);
  4256. int eCol = qMin(endColumn + 1, block.length() - 1);
  4257. tc.setPosition(block.position() + bCol, MoveAnchor);
  4258. tc.setPosition(block.position() + eCol, KeepAnchor);
  4259. TransformationData td(tc.selectedText(), extra);
  4260. (this->*transformFunc)(&td);
  4261. tc.removeSelectedText();
  4262. tc.insertText(td.to);
  4263. block = block.previous();
  4264. }
  4265. endEditBlock();
  4266. }
  4267. }
  4268. }
  4269. void FakeVimHandler::Private::insertText(const Register &reg)
  4270. {
  4271. QTC_ASSERT(reg.rangemode == RangeCharMode,
  4272. qDebug() << "WRONG INSERT MODE: " << reg.rangemode; return);
  4273. setAnchor();
  4274. cursor().insertText(reg.contents);
  4275. m_lastChangePosition = cursor().position();
  4276. //dump("AFTER INSERT");
  4277. }
  4278. void FakeVimHandler::Private::removeText(const Range &range)
  4279. {
  4280. //qDebug() << "REMOVE: " << range;
  4281. transformText(range, &FakeVimHandler::Private::removeTransform);
  4282. }
  4283. void FakeVimHandler::Private::removeTransform(TransformationData *td)
  4284. {
  4285. Q_UNUSED(td);
  4286. }
  4287. void FakeVimHandler::Private::downCase(const Range &range)
  4288. {
  4289. transformText(range, &FakeVimHandler::Private::downCaseTransform);
  4290. }
  4291. void FakeVimHandler::Private::downCaseTransform(TransformationData *td)
  4292. {
  4293. td->to = td->from.toLower();
  4294. }
  4295. void FakeVimHandler::Private::upCase(const Range &range)
  4296. {
  4297. transformText(range, &FakeVimHandler::Private::upCaseTransform);
  4298. }
  4299. void FakeVimHandler::Private::upCaseTransform(TransformationData *td)
  4300. {
  4301. td->to = td->from.toUpper();
  4302. }
  4303. void FakeVimHandler::Private::invertCase(const Range &range)
  4304. {
  4305. transformText(range, &FakeVimHandler::Private::invertCaseTransform);
  4306. }
  4307. void FakeVimHandler::Private::invertCaseTransform(TransformationData *td)
  4308. {
  4309. foreach (QChar c, td->from)
  4310. td->to += c.isUpper() ? c.toLower() : c.toUpper();
  4311. }
  4312. void FakeVimHandler::Private::replaceText(const Range &range, const QString &str)
  4313. {
  4314. Transformation tr = &FakeVimHandler::Private::replaceByStringTransform;
  4315. transformText(range, tr, str);
  4316. }
  4317. void FakeVimHandler::Private::replaceByStringTransform(TransformationData *td)
  4318. {
  4319. td->to = td->extraData.toString();
  4320. }
  4321. void FakeVimHandler::Private::replaceByCharTransform(TransformationData *td)
  4322. {
  4323. td->to = QString(td->from.size(), td->extraData.toChar());
  4324. }
  4325. void FakeVimHandler::Private::pasteText(bool afterCursor)
  4326. {
  4327. const QString text = registerContents(m_register);
  4328. const QStringList lines = text.split(QChar('\n'));
  4329. beginEditBlock();
  4330. if (isVisualCharMode()) {
  4331. leaveVisualMode();
  4332. m_submode = DeleteSubMode;
  4333. finishMovement();
  4334. } else if (isVisualLineMode()) {
  4335. leaveVisualMode();
  4336. m_rangemode = RangeLineMode;
  4337. yankText(currentRange(), m_register);
  4338. removeText(currentRange());
  4339. handleStartOfLine();
  4340. } else if (isVisualBlockMode()) {
  4341. leaveVisualMode();
  4342. m_rangemode = RangeBlockMode;
  4343. yankText(currentRange(), m_register);
  4344. removeText(currentRange());
  4345. setPosition(qMin(position(), anchor()));
  4346. }
  4347. switch (registerRangeMode(m_register)) {
  4348. case RangeCharMode: {
  4349. m_targetColumn = 0;
  4350. for (int i = count(); --i >= 0; ) {
  4351. if (afterCursor && rightDist() > 0)
  4352. moveRight();
  4353. setAnchor();
  4354. insertText(text);
  4355. if (!afterCursor && atEndOfLine())
  4356. moveLeft();
  4357. moveLeft();
  4358. }
  4359. break;
  4360. }
  4361. case RangeLineMode:
  4362. case RangeLineModeExclusive: {
  4363. moveToStartOfLine();
  4364. m_targetColumn = 0;
  4365. QTextCursor tc = cursor();
  4366. for (int i = count(); --i >= 0; ) {
  4367. bool lastLine = document()->lastBlock() == this->block();
  4368. if (afterCursor) {
  4369. if (lastLine) {
  4370. tc.movePosition(EndOfLine, MoveAnchor);
  4371. tc.insertBlock();
  4372. }
  4373. moveDown();
  4374. }
  4375. setAnchor();
  4376. insertText(text);
  4377. if (afterCursor && lastLine) {
  4378. tc.movePosition(Left, KeepAnchor);
  4379. tc.removeSelectedText();
  4380. setAnchor();
  4381. moveUp(lines.size() - 2);
  4382. } else {
  4383. moveUp(lines.size() - 1);
  4384. }
  4385. }
  4386. moveToFirstNonBlankOnLine();
  4387. break;
  4388. }
  4389. case RangeBlockAndTailMode:
  4390. case RangeBlockMode: {
  4391. QTextBlock block = this->block();
  4392. if (afterCursor)
  4393. moveRight();
  4394. QTextCursor tc = cursor();
  4395. const int col = tc.position() - block.position();
  4396. //for (int i = lines.size(); --i >= 0; ) {
  4397. for (int i = 0; i < lines.size(); ++i) {
  4398. const QString line = lines.at(i);
  4399. tc.movePosition(StartOfLine, MoveAnchor);
  4400. if (col >= block.length()) {
  4401. tc.movePosition(EndOfLine, MoveAnchor);
  4402. tc.insertText(QString(col - line.size() + 1, QChar(' ')));
  4403. } else {
  4404. tc.movePosition(Right, MoveAnchor, col - 1 + afterCursor);
  4405. }
  4406. //qDebug() << "INSERT " << line << " AT " << tc.position()
  4407. // << "COL: " << col;
  4408. tc.insertText(line);
  4409. tc.movePosition(StartOfLine, MoveAnchor);
  4410. tc.movePosition(Down, MoveAnchor, 1);
  4411. if (tc.position() >= lastPositionInDocument() - 1) {
  4412. tc.insertText(QString(QChar('\n')));
  4413. tc.movePosition(Up, MoveAnchor, 1);
  4414. }
  4415. block = block.next();
  4416. }
  4417. moveLeft();
  4418. break;
  4419. }
  4420. }
  4421. endEditBlock();
  4422. }
  4423. QString FakeVimHandler::Private::lineContents(int line) const
  4424. {
  4425. return document()->findBlockByNumber(line - 1).text();
  4426. }
  4427. void FakeVimHandler::Private::setLineContents(int line, const QString &contents)
  4428. {
  4429. QTextBlock block = document()->findBlockByNumber(line - 1);
  4430. QTextCursor tc = cursor();
  4431. const int begin = block.position();
  4432. const int len = block.length();
  4433. tc.setPosition(begin);
  4434. tc.setPosition(begin + len - 1, KeepAnchor);
  4435. tc.removeSelectedText();
  4436. tc.insertText(contents);
  4437. }
  4438. int FakeVimHandler::Private::firstPositionInLine(int line) const
  4439. {
  4440. return document()->findBlockByNumber(line - 1).position();
  4441. }
  4442. int FakeVimHandler::Private::lastPositionInLine(int line) const
  4443. {
  4444. QTextBlock block = document()->findBlockByNumber(line - 1);
  4445. return block.position() + block.length() - 1;
  4446. }
  4447. int FakeVimHandler::Private::lineForPosition(int pos) const
  4448. {
  4449. QTextCursor tc = cursor();
  4450. tc.setPosition(pos);
  4451. return tc.block().blockNumber() + 1;
  4452. }
  4453. void FakeVimHandler::Private::toggleVisualMode(VisualMode visualMode)
  4454. {
  4455. if (isVisualMode()) {
  4456. leaveVisualMode();
  4457. } else {
  4458. m_positionPastEnd = false;
  4459. m_anchorPastEnd = false;
  4460. m_visualMode = visualMode;
  4461. const int pos = position();
  4462. //setMark('<', pos);
  4463. //setMark('>', pos + 1);
  4464. setAnchorAndPosition(pos, pos);
  4465. updateMiniBuffer();
  4466. updateSelection();
  4467. }
  4468. }
  4469. void FakeVimHandler::Private::leaveVisualMode()
  4470. {
  4471. if (isVisualLineMode())
  4472. m_movetype = MoveLineWise;
  4473. else if (isVisualCharMode())
  4474. m_movetype = MoveInclusive;
  4475. m_visualMode = NoVisualMode;
  4476. updateMiniBuffer();
  4477. updateSelection();
  4478. }
  4479. QWidget *FakeVimHandler::Private::editor() const
  4480. {
  4481. return m_textedit
  4482. ? static_cast<QWidget *>(m_textedit)
  4483. : static_cast<QWidget *>(m_plaintextedit);
  4484. }
  4485. void FakeVimHandler::Private::undo()
  4486. {
  4487. // FIXME: That's only an approximaxtion. The real solution might
  4488. // be to store marks and old userData with QTextBlock setUserData
  4489. // and retrieve them afterward.
  4490. const int current = document()->availableUndoSteps();
  4491. EDITOR(undo());
  4492. const int rev = document()->availableUndoSteps();
  4493. if (current == rev)
  4494. showBlackMessage(FakeVimHandler::tr("Already at oldest change"));
  4495. else
  4496. showBlackMessage(QString());
  4497. if (m_undoCursorPosition.contains(rev))
  4498. setPosition(m_undoCursorPosition[rev]);
  4499. setTargetColumn();
  4500. if (atEndOfLine())
  4501. moveLeft();
  4502. }
  4503. void FakeVimHandler::Private::redo()
  4504. {
  4505. const int current = document()->availableUndoSteps();
  4506. EDITOR(redo());
  4507. const int rev = document()->availableUndoSteps();
  4508. if (rev == current)
  4509. showBlackMessage(FakeVimHandler::tr("Already at newest change"));
  4510. else
  4511. showBlackMessage(QString());
  4512. if (m_undoCursorPosition.contains(rev))
  4513. setPosition(m_undoCursorPosition[rev]);
  4514. setTargetColumn();
  4515. }
  4516. void FakeVimHandler::Private::updateCursorShape()
  4517. {
  4518. bool thinCursor = m_mode == ExMode
  4519. || m_subsubmode == SearchSubSubMode
  4520. || m_mode == InsertMode
  4521. || isVisualMode()
  4522. || cursor().hasSelection();
  4523. EDITOR(setOverwriteMode(!thinCursor));
  4524. }
  4525. void FakeVimHandler::Private::enterReplaceMode()
  4526. {
  4527. m_mode = ReplaceMode;
  4528. m_submode = NoSubMode;
  4529. m_subsubmode = NoSubSubMode;
  4530. m_commandPrefix.clear();
  4531. m_lastInsertion.clear();
  4532. m_lastDeletion.clear();
  4533. }
  4534. void FakeVimHandler::Private::enterInsertMode()
  4535. {
  4536. m_mode = InsertMode;
  4537. m_submode = NoSubMode;
  4538. m_subsubmode = NoSubSubMode;
  4539. m_commandPrefix.clear();
  4540. m_lastInsertion.clear();
  4541. m_lastDeletion.clear();
  4542. }
  4543. void FakeVimHandler::Private::enterCommandMode()
  4544. {
  4545. if (atEndOfLine())
  4546. moveLeft();
  4547. m_mode = CommandMode;
  4548. m_submode = NoSubMode;
  4549. m_subsubmode = NoSubSubMode;
  4550. m_commandPrefix.clear();
  4551. }
  4552. void FakeVimHandler::Private::enterExMode()
  4553. {
  4554. m_mode = ExMode;
  4555. m_submode = NoSubMode;
  4556. m_subsubmode = NoSubSubMode;
  4557. m_commandPrefix = ':';
  4558. }
  4559. void FakeVimHandler::Private::recordJump()
  4560. {
  4561. m_jumpListUndo.append(cursorPosition());
  4562. m_jumpListRedo.clear();
  4563. UNDO_DEBUG("jumps: " << m_jumpListUndo);
  4564. }
  4565. Column FakeVimHandler::Private::indentation(const QString &line) const
  4566. {
  4567. int ts = config(ConfigTabStop).toInt();
  4568. int physical = 0;
  4569. int logical = 0;
  4570. int n = line.size();
  4571. while (physical < n) {
  4572. QChar c = line.at(physical);
  4573. if (c == QLatin1Char(' '))
  4574. ++logical;
  4575. else if (c == QLatin1Char('\t'))
  4576. logical += ts - logical % ts;
  4577. else
  4578. break;
  4579. ++physical;
  4580. }
  4581. return Column(physical, logical);
  4582. }
  4583. QString FakeVimHandler::Private::tabExpand(int n) const
  4584. {
  4585. int ts = config(ConfigTabStop).toInt();
  4586. if (hasConfig(ConfigExpandTab) || ts < 1)
  4587. return QString(n, QLatin1Char(' '));
  4588. return QString(n / ts, QLatin1Char('\t'))
  4589. + QString(n % ts, QLatin1Char(' '));
  4590. }
  4591. void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
  4592. {
  4593. if (!hasConfig(ConfigAutoIndent) && !hasConfig(ConfigSmartIndent))
  4594. return;
  4595. if (hasConfig(ConfigSmartIndent)) {
  4596. QTextBlock bl = block();
  4597. Range range(bl.position(), bl.position());
  4598. const int oldSize = bl.text().size();
  4599. indentText(range, QLatin1Char('\n'));
  4600. m_justAutoIndented = bl.text().size() - oldSize;
  4601. } else {
  4602. QTextBlock bl = goingDown ? block().previous() : block().next();
  4603. QString text = bl.text();
  4604. int pos = 0;
  4605. int n = text.size();
  4606. while (pos < n && text.at(pos).isSpace())
  4607. ++pos;
  4608. text.truncate(pos);
  4609. // FIXME: handle 'smartindent' and 'cindent'
  4610. insertText(text);
  4611. m_justAutoIndented = text.size();
  4612. }
  4613. }
  4614. bool FakeVimHandler::Private::removeAutomaticIndentation()
  4615. {
  4616. if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
  4617. return false;
  4618. /*
  4619. m_tc.movePosition(StartOfLine, KeepAnchor);
  4620. m_tc.removeSelectedText();
  4621. m_lastInsertion.chop(m_justAutoIndented);
  4622. */
  4623. m_justAutoIndented = 0;
  4624. return true;
  4625. }
  4626. void FakeVimHandler::Private::handleStartOfLine()
  4627. {
  4628. if (hasConfig(ConfigStartOfLine))
  4629. moveToFirstNonBlankOnLine();
  4630. }
  4631. void FakeVimHandler::Private::replay(const QString &command, int n)
  4632. {
  4633. //qDebug() << "REPLAY: " << quoteUnprintable(command);
  4634. for (int i = n; --i >= 0; ) {
  4635. foreach (QChar c, command) {
  4636. //qDebug() << " REPLAY: " << c.unicode();
  4637. handleKey(Input(c));
  4638. }
  4639. }
  4640. }
  4641. void FakeVimHandler::Private::selectWordTextObject(bool inner)
  4642. {
  4643. Q_UNUSED(inner); // FIXME
  4644. m_movetype = MoveExclusive;
  4645. moveToWordBoundary(false, false, true);
  4646. setAnchor();
  4647. // FIXME: Rework the 'anchor' concept.
  4648. //if (isVisualMode())
  4649. // setMark('<', cursor().position());
  4650. moveToWordBoundary(false, true, true);
  4651. setTargetColumn();
  4652. m_movetype = MoveInclusive;
  4653. }
  4654. void FakeVimHandler::Private::selectWORDTextObject(bool inner)
  4655. {
  4656. Q_UNUSED(inner); // FIXME
  4657. m_movetype = MoveExclusive;
  4658. moveToWordBoundary(true, false, true);
  4659. setAnchor();
  4660. // FIXME: Rework the 'anchor' concept.
  4661. //if (isVisualMode())
  4662. // setMark('<', cursor().position());
  4663. moveToWordBoundary(true, true, true);
  4664. setTargetColumn();
  4665. m_movetype = MoveInclusive;
  4666. }
  4667. void FakeVimHandler::Private::selectSentenceTextObject(bool inner)
  4668. {
  4669. Q_UNUSED(inner);
  4670. }
  4671. void FakeVimHandler::Private::selectParagraphTextObject(bool inner)
  4672. {
  4673. Q_UNUSED(inner);
  4674. }
  4675. void FakeVimHandler::Private::selectBlockTextObject(bool inner,
  4676. char left, char right)
  4677. {
  4678. QString sleft = QString(QLatin1Char(left));
  4679. QString sright = QString(QLatin1Char(right));
  4680. QTextCursor tc2 = document()->find(sright, cursor());
  4681. if (tc2.isNull())
  4682. return;
  4683. QTextCursor tc1 = document()->find(sleft, cursor(), QTextDocument::FindBackward);
  4684. if (tc1.isNull())
  4685. return;
  4686. int p1 = tc1.position() + inner - sleft.size();
  4687. if (inner && document()->characterAt(p1) == ParagraphSeparator)
  4688. ++p1;
  4689. const int p2 = tc2.position() - inner - sright.size();
  4690. setAnchorAndPosition(p2, p1);
  4691. m_movetype = MoveInclusive;
  4692. }
  4693. static bool isSign(const QChar c)
  4694. {
  4695. return c.unicode() == '-' || c.unicode() == '+';
  4696. }
  4697. void FakeVimHandler::Private::changeNumberTextObject(bool doIncrement)
  4698. {
  4699. QTextCursor tc = cursor();
  4700. int pos = tc.position();
  4701. const int n = lastPositionInDocument();
  4702. QTextDocument *doc = document();
  4703. QChar c = doc->characterAt(pos);
  4704. if (!c.isNumber()) {
  4705. if (pos == n || !isSign(c))
  4706. return;
  4707. ++pos;
  4708. c = doc->characterAt(pos);
  4709. if (!c.isNumber())
  4710. return;
  4711. }
  4712. int p1 = pos;
  4713. while (p1 >= 1 && doc->characterAt(p1 - 1).isNumber())
  4714. --p1;
  4715. if (p1 >= 1 && isSign(doc->characterAt(p1 - 1)))
  4716. --p1;
  4717. int p2 = pos;
  4718. while (p2 <= n - 1 && doc->characterAt(p2 + 1).isNumber())
  4719. ++p2;
  4720. ++p2;
  4721. setAnchorAndPosition(p2, p1);
  4722. QString orig = selectText(currentRange());
  4723. int value = orig.toInt();
  4724. value = doIncrement ? value + 1 : value - 1;
  4725. QString repl = QString::fromLatin1("%1").arg(value, orig.size(), 10, QLatin1Char('0'));
  4726. replaceText(currentRange(), repl);
  4727. moveLeft();
  4728. }
  4729. void FakeVimHandler::Private::selectQuotedStringTextObject(bool inner, int type)
  4730. {
  4731. Q_UNUSED(inner);
  4732. Q_UNUSED(type);
  4733. }
  4734. int FakeVimHandler::Private::mark(int code) const
  4735. {
  4736. // FIXME: distinguish local and global marks.
  4737. //qDebug() << "MARK: " << code << m_marks.value(code, -1) << m_marks;
  4738. if (isVisualMode()) {
  4739. if (code == '<')
  4740. return position();
  4741. if (code == '>')
  4742. return anchor();
  4743. }
  4744. if (code == '.')
  4745. return m_lastChangePosition;
  4746. QTextCursor tc = m_marks.value(code);
  4747. return tc.isNull() ? -1 : tc.position();
  4748. }
  4749. void FakeVimHandler::Private::setMark(int code, int position)
  4750. {
  4751. // FIXME: distinguish local and global marks.
  4752. QTextCursor tc = cursor();
  4753. tc.setPosition(position, MoveAnchor);
  4754. m_marks[code] = tc;
  4755. }
  4756. void FakeVimHandler::Private::setRegisterRangeMode(int reg, RangeMode mode)
  4757. {
  4758. g.registers[reg].rangemode = mode;
  4759. }
  4760. RangeMode FakeVimHandler::Private::registerRangeMode(int reg) const
  4761. {
  4762. return g.registers[reg].rangemode;
  4763. }
  4764. void FakeVimHandler::Private::setRegisterContents(int reg, const QString &contents)
  4765. {
  4766. g.registers[reg].contents = contents;
  4767. }
  4768. QString FakeVimHandler::Private::registerContents(int reg) const
  4769. {
  4770. return g.registers[reg].contents;
  4771. }
  4772. ///////////////////////////////////////////////////////////////////////
  4773. //
  4774. // FakeVimHandler
  4775. //
  4776. ///////////////////////////////////////////////////////////////////////
  4777. FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
  4778. : QObject(parent), d(new Private(this, widget))
  4779. {}
  4780. FakeVimHandler::~FakeVimHandler()
  4781. {
  4782. delete d;
  4783. }
  4784. // gracefully handle that the parent editor is deleted
  4785. void FakeVimHandler::disconnectFromEditor()
  4786. {
  4787. d->m_textedit = 0;
  4788. d->m_plaintextedit = 0;
  4789. }
  4790. bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
  4791. {
  4792. bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
  4793. // Catch mouse events on the viewport.
  4794. QWidget *viewport = 0;
  4795. if (d->m_plaintextedit)
  4796. viewport = d->m_plaintextedit->viewport();
  4797. else if (d->m_textedit)
  4798. viewport = d->m_textedit->viewport();
  4799. if (ob == viewport) {
  4800. if (active && ev->type() == QEvent::MouseButtonRelease) {
  4801. QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
  4802. if (mev->button() == Qt::LeftButton) {
  4803. d->importSelection();
  4804. //return true;
  4805. }
  4806. }
  4807. if (active && ev->type() == QEvent::MouseButtonPress) {
  4808. QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
  4809. if (mev->button() == Qt::LeftButton) {
  4810. d->m_visualMode = NoVisualMode;
  4811. d->updateSelection();
  4812. }
  4813. }
  4814. return QObject::eventFilter(ob, ev);
  4815. }
  4816. if (active && ev->type() == QEvent::Shortcut) {
  4817. d->passShortcuts(false);
  4818. return false;
  4819. }
  4820. if (active && ev->type() == QEvent::InputMethod && ob == d->editor()) {
  4821. // This handles simple dead keys. The sequence of events is
  4822. // KeyRelease-InputMethod-KeyRelease for dead keys instead of
  4823. // KeyPress-KeyRelease as for simple keys. As vi acts on key presses,
  4824. // we have to act on the InputMethod event.
  4825. // FIXME: A first approximation working for e.g. ^ on a German keyboard
  4826. QInputMethodEvent *imev = static_cast<QInputMethodEvent *>(ev);
  4827. KEY_DEBUG("INPUTMETHOD" << imev->commitString() << imev->preeditString());
  4828. QString commitString = imev->commitString();
  4829. int key = commitString.size() == 1 ? commitString.at(0).unicode() : 0;
  4830. QKeyEvent kev(QEvent::KeyPress, key, Qt::KeyboardModifiers(), commitString);
  4831. EventResult res = d->handleEvent(&kev);
  4832. return res == EventHandled;
  4833. }
  4834. if (active && ev->type() == QEvent::KeyPress && ob == d->editor()) {
  4835. QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
  4836. KEY_DEBUG("KEYPRESS" << kev->key() << kev->text() << QChar(kev->key()));
  4837. EventResult res = d->handleEvent(kev);
  4838. //if (d->m_mode == InsertMode)
  4839. // emit completionRequested();
  4840. // returning false core the app see it
  4841. //KEY_DEBUG("HANDLED CODE:" << res);
  4842. //return res != EventPassedToCore;
  4843. //return true;
  4844. return res == EventHandled;
  4845. }
  4846. if (active && ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
  4847. QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
  4848. if (d->wantsOverride(kev)) {
  4849. KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
  4850. ev->accept(); // accepting means "don't run the shortcuts"
  4851. return true;
  4852. }
  4853. KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
  4854. return true;
  4855. }
  4856. if (active && ev->type() == QEvent::FocusIn && ob == d->editor()) {
  4857. d->stopIncrementalFind();
  4858. }
  4859. return QObject::eventFilter(ob, ev);
  4860. }
  4861. void FakeVimHandler::installEventFilter()
  4862. {
  4863. d->installEventFilter();
  4864. }
  4865. void FakeVimHandler::setupWidget()
  4866. {
  4867. d->setupWidget();
  4868. }
  4869. void FakeVimHandler::restoreWidget(int tabSize)
  4870. {
  4871. d->restoreWidget(tabSize);
  4872. }
  4873. void FakeVimHandler::handleCommand(const QString &cmd)
  4874. {
  4875. d->handleCommand(cmd);
  4876. }
  4877. void FakeVimHandler::handleReplay(const QString &keys)
  4878. {
  4879. d->replay(keys, 1);
  4880. }
  4881. void FakeVimHandler::handleInput(const QString &keys)
  4882. {
  4883. Mode oldMode = d->m_mode;
  4884. d->m_mode = CommandMode;
  4885. Inputs inputs;
  4886. inputs.parseFrom(keys);
  4887. foreach (const Input &input, inputs)
  4888. d->handleKey(input);
  4889. d->m_mode = oldMode;
  4890. }
  4891. void FakeVimHandler::setCurrentFileName(const QString &fileName)
  4892. {
  4893. d->m_currentFileName = fileName;
  4894. }
  4895. QString FakeVimHandler::currentFileName() const
  4896. {
  4897. return d->m_currentFileName;
  4898. }
  4899. void FakeVimHandler::showBlackMessage(const QString &msg)
  4900. {
  4901. d->showBlackMessage(msg);
  4902. }
  4903. void FakeVimHandler::showRedMessage(const QString &msg)
  4904. {
  4905. d->showRedMessage(msg);
  4906. }
  4907. QWidget *FakeVimHandler::widget()
  4908. {
  4909. return d->editor();
  4910. }
  4911. // Test only
  4912. int FakeVimHandler::physicalIndentation(const QString &line) const
  4913. {
  4914. Column ind = d->indentation(line);
  4915. return ind.physical;
  4916. }
  4917. int FakeVimHandler::logicalIndentation(const QString &line) const
  4918. {
  4919. Column ind = d->indentation(line);
  4920. return ind.logical;
  4921. }
  4922. QString FakeVimHandler::tabExpand(int n) const
  4923. {
  4924. return d->tabExpand(n);
  4925. }
  4926. } // namespace Internal
  4927. } // namespace FakeVim
  4928. #include "fakevimhandler.moc"