fakevimhandler.cpp 126 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
hjk's avatar
hjk committed
8
**
9
** Commercial Usage
hjk's avatar
hjk committed
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
hjk's avatar
hjk committed
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
hjk's avatar
hjk committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

30
#include "fakevimhandler.h"
hjk's avatar
hjk committed
31

32
//
33
34
35
36
37
38
39
40
41
42
43
// ATTENTION:
//
// 1 Please do not add any direct dependencies to other Qt Creator code here.
//   Instead emit signals and let the FakeVimPlugin channel the information to
//   Qt Creator. The idea is to keep this file here in a "clean" state that
//   allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
//
// 2 There are a few auto tests located in ../../../tests/auto/fakevim.
//   Commands that are covered there are marked as "// tested" below.
//
// 3 Some conventions:
44
//
45
46
47
//   Use 1 based line numbers and 0 based column numbers. Even though
//   the 1 based line are not nice it matches vim's and QTextEdit's 'line'
//   concepts.
48
//
49
50
51
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
52
53
54
55
//   A QTextCursor is always between characters, whereas vi's cursor is always
//   over a character. FakeVim interprets the QTextCursor to be over the character
//   to the right of the QTextCursor's position().
//
56
//   There is always a "current" cursor (m_tc). A current "region of interest"
57
58
59
//   spans between m_anchor (== anchor()), i.e. the character below anchor()), and
//   m_tc.position() (== position()). The character below position() is not included
//   if the last movement command was exclusive (MoveExclusive).
60
//   The value of m_tc.anchor() is not used.
61

hjk's avatar
hjk committed
62
#include <utils/qtcassert.h>
63

hjk's avatar
hjk committed
64
#include <QtCore/QDebug>
65
#include <QtCore/QFile>
hjk's avatar
hjk committed
66
#include <QtCore/QObject>
67
#include <QtCore/QPointer>
hjk's avatar
hjk committed
68
#include <QtCore/QProcess>
hjk's avatar
hjk committed
69
#include <QtCore/QRegExp>
70
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
71
#include <QtCore/QtAlgorithms>
hjk's avatar
hjk committed
72
73
#include <QtCore/QStack>

hjk's avatar
hjk committed
74
#include <QtGui/QApplication>
hjk's avatar
hjk committed
75
76
77
78
79
80
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
81
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
82
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
83

84
#include <algorithm>
85
#include <climits>
86
#include <ctype.h>
87

88
89
90
91
92
93
94
//#define DEBUG_KEY  1
#if DEBUG_KEY
#   define KEY_DEBUG(s) qDebug() << s
#else
#   define KEY_DEBUG(s)
#endif

95
96
//#define DEBUG_UNDO  1
#if DEBUG_UNDO
97
#   define UNDO_DEBUG(s) qDebug() << << m_tc.document()->availableUndoSteps() << s
98
99
100
101
#else
#   define UNDO_DEBUG(s)
#endif

102
using namespace Utils;
hjk's avatar
hjk committed
103
104
105
106

namespace FakeVim {
namespace Internal {

hjk's avatar
hjk committed
107
108
109
110
111
112
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
113
114
115
116
117
118
119
120
121
122
#define StartOfLine     QTextCursor::StartOfLine
#define EndOfLine       QTextCursor::EndOfLine
#define MoveAnchor      QTextCursor::MoveAnchor
#define KeepAnchor      QTextCursor::KeepAnchor
#define Up              QTextCursor::Up
#define Down            QTextCursor::Down
#define Right           QTextCursor::Right
#define Left            QTextCursor::Left
#define EndOfDocument   QTextCursor::End
#define StartOfDocument QTextCursor::Start
hjk's avatar
hjk committed
123

124
125
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
126
127
128
129
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

130

hjk's avatar
hjk committed
131
132
133
134
enum Mode
{
    InsertMode,
    CommandMode,
135
136
137
    ExMode,
    SearchForwardMode,
    SearchBackwardMode,
hjk's avatar
hjk committed
138
};
hjk's avatar
hjk committed
139

hjk's avatar
hjk committed
140
141
142
enum SubMode
{
    NoSubMode,
143
144
145
    ChangeSubMode,     // used for c
    DeleteSubMode,     // used for d
    FilterSubMode,     // used for !
hjk's avatar
hjk committed
146
147
    IndentSubMode,     // used for =
    RegisterSubMode,   // used for "
hjk's avatar
hjk committed
148
    ReplaceSubMode,    // used for R and r
149
150
    ShiftLeftSubMode,  // used for <
    ShiftRightSubMode, // used for >
151
    TransformSubMode,  // used for ~/gu/gU
hjk's avatar
hjk committed
152
153
    WindowSubMode,     // used for Ctrl-w
    YankSubMode,       // used for y
154
155
    ZSubMode,          // used for z
    CapitalZSubMode    // used for Z
hjk's avatar
hjk committed
156
};
hjk's avatar
hjk committed
157

hjk's avatar
hjk committed
158
159
enum SubSubMode
{
hjk's avatar
hjk committed
160
161
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
162
    NoSubSubMode,
163
164
165
166
    FtSubSubMode,         // used for f, F, t, T
    MarkSubSubMode,       // used for m
    BackTickSubSubMode,   // used for `
    TickSubSubMode,       // used for '
167
    InvertCaseSubSubMode, // used for ~
168
169
170
171
    DownCaseSubSubMode,   // used for gu
    UpCaseSubSubMode,     // used for gU
    ReplaceSubSubMode,    // used for r after visual mode
    TextObjectSubSubMode, // used for f, F, t, T
hjk's avatar
hjk committed
172
173
};

174
175
176
177
178
179
180
181
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

hjk's avatar
hjk committed
182
183
184
185
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
hjk's avatar
hjk committed
186
    MoveLineWise,
hjk's avatar
hjk committed
187
188
};

189
190
enum RangeMode
{
191
192
    RangeCharMode,  // v
    RangeLineMode,  // V
193
    RangeLineModeExclusive, // like above, but keep one newline when deleting
194
195
    RangeBlockMode, // Ctrl-v
    RangeBlockAndTailMode, // Ctrl-v for D and X
196
197
198
199
200
201
202
203
204
};

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

205
struct Column
206
{
207
208
209
    Column(int p, int l) : physical(p), logical(l) {}
    int physical; // number of characters in the data
    int logical; // column on screen
210
211
};

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
struct CursorPosition
{
    // for jump history
    CursorPosition() : position(-1), scrollLine(-1) {}
    CursorPosition(int pos, int line) : position(pos), scrollLine(line) {}
    int position; // Position in document
    int scrollLine; // First visible line
};

struct Register
{
    Register() : rangemode(RangeCharMode) {}
    Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
    QString contents;
    RangeMode rangemode;
};

struct Range
{
    Range()
        : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
    {}

    Range(int b, int e, RangeMode m = RangeCharMode)
        : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
con's avatar
con committed
237
    {}
238

239
    QString toString() const
240
241
242
243
244
    {
        return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
            .arg(rangemode);
    }

245
246
247
248
249
    int beginPos;
    int endPos;
    RangeMode rangemode;
};

250
251
QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
{
252
    foreach (const QTextEdit::ExtraSelection &sel, sels)
253
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
254
255
    return ts;
}
256

hjk's avatar
hjk committed
257
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
258
{
hjk's avatar
hjk committed
259
260
261
262
263
264
265
266
267
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
        QChar c = ba.at(i);
        if (c.isPrint())
            res += c;
        else
            res += QString("\\x%1").arg(c.unicode(), 2, 16);
    }
    return res;
hjk's avatar
hjk committed
268
269
}

270
271
272
273
274
275
276
277
278
279
280
static bool startsWithWhitespace(const QString &str, int col)
{
    QTC_ASSERT(str.size() >= col, return false);
    for (int i = 0; i < col; ++i) {
        uint u = str.at(i).unicode();
        if (u != ' ' && u != '\t')
            return false;
    }
    return true;
}

281
282
283
284
285
inline QString msgE20MarkNotSet(const QString &text)
{
    return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text);
}

hjk's avatar
hjk committed
286
287
288
class FakeVimHandler::Private
{
public:
289
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
290

291
292
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
293
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
294
    void handleExCommand(const QString &cmd);
295
296
297

    // updates marks positions by the difference in positionChange
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
298

299
    void installEventFilter();
300
    void passShortcuts(bool enable);
301
302
303
    void setupWidget();
    void restoreWidget();

304
    friend class FakeVimHandler;
hjk's avatar
hjk committed
305
306
307
308
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
309
310
311
312
313
    EventResult handleKey(int key, int unmodified, const QString &text);
    EventResult handleInsertMode(int key, int unmodified, const QString &text);
    EventResult handleCommandMode(int key, int unmodified, const QString &text);
    EventResult handleRegisterMode(int key, int unmodified, const QString &text);
    EventResult handleMiniBufferModes(int key, int unmodified, const QString &text);
hjk's avatar
hjk committed
314
    EventResult handleCommandSubSubMode(int key, int unmodified, const QString &text);
315
316
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
317
    void resetCommandMode();
hjk's avatar
hjk committed
318
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
319
    void highlightMatches(const QString &needle);
320
    void stopIncrementalFind();
hjk's avatar
hjk committed
321

hjk's avatar
hjk committed
322
323
324
    int mvCount() const { return m_mvcount.isEmpty() ? 1 : m_mvcount.toInt(); }
    int opCount() const { return m_opcount.isEmpty() ? 1 : m_opcount.toInt(); }
    int count() const { return mvCount() * opCount(); }
hjk's avatar
hjk committed
325
326
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
327
328
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
329

hjk's avatar
hjk committed
330
    int lastPositionInDocument() const; // last valid pos in doc
331
332
    int firstPositionInLine(int line) const; // 1 based line, 0 based pos
    int lastPositionInLine(int line) const; // 1 based line, 0 based pos
hjk's avatar
hjk committed
333
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
334
335
    QString lineContents(int line) const; // 1 based line
    void setLineContents(int line, const QString &contents) const; // 1 based line
hjk's avatar
hjk committed
336

hjk's avatar
hjk committed
337
338
    int linesOnScreen() const;
    int columnsOnScreen() const;
339
340
341
342
    int linesInDocument() const;

    // all zero-based counting
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
343
    int cursorLineInDocument() const;
344
345
346
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
347
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
348
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
349
350
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
351

352
353
354
355
356
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

357
    // helper functions for indenting
358
    bool isElectricCharacter(QChar c) const;
359
360
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
361
362
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
363

hjk's avatar
hjk committed
364
    void moveToFirstNonBlankOnLine();
365
    void moveToTargetColumn();
366
367
    void setTargetColumn() {
        m_targetColumn = leftDist();
368
        m_visualTargetColumn = m_targetColumn;
369
370
        //qDebug() << "TARGET: " << m_targetColumn;
    }
371
    void moveToNextWord(bool simple, bool deleteWord = false);
372
    void moveToMatchingParanthesis();
373
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
374
375

    // to reduce line noise
hjk's avatar
hjk committed
376
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
377
    void moveToStartOfLine();
hjk's avatar
hjk committed
378
    void moveToEndOfLine();
hjk's avatar
hjk committed
379
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
380
381
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
382
383
    void moveRight(int n = 1) { m_tc.movePosition(Right, MoveAnchor, n); }
    void moveLeft(int n = 1) { m_tc.movePosition(Left, MoveAnchor, n); }
384
385
    void setAnchor() { if (!isVisualMode()) m_anchor = m_tc.position(); }
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
386
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
387

388
    bool handleFfTt(int key);
hjk's avatar
hjk committed
389

hjk's avatar
hjk committed
390
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
391
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
392
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
393

394
    void enterInsertMode();
395
    void enterCommandMode();
396
    void enterExMode();
hjk's avatar
hjk committed
397
398
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
399
    void notImplementedYet();
400
401
    void updateMiniBuffer();
    void updateSelection();
402
    QWidget *editor() const;
403
404
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
405
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
406
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
407
408
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
409

410
411
412
    // this asks the layer above (e.g. the fake vim plugin or the
    // stand-alone test application to handle the command)
    void passUnknownExCommand(const QString &cmd);
413
414
415
    // this asks the layer above (e.g. the fake vim plugin or the
    // stand-alone test application to handle the set command)
    void passUnknownSetCommand(const QString &cmd);
416

417
418
419
420
421
    bool isVisualMode() const { return m_visualMode != NoVisualMode; }
    bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
    bool isVisualCharMode() const { return m_visualMode == VisualCharMode; }
    bool isVisualLineMode() const { return m_visualMode == VisualLineMode; }
    bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; }
hjk's avatar
hjk committed
422
    void updateEditor();
423

424
425
426
427
428
429
430
    void selectWordTextObject(bool inner);
    void selectWORDTextObject(bool inner);
    void selectSentenceTextObject(bool inner);
    void selectParagraphTextObject(bool inner);
    void selectBlockTextObject(bool inner, char left, char right);
    void selectQuotedStringTextObject(bool inner, int type);

431
432
433
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
434
    bool m_wasReadOnly; // saves read-only state of document
435

hjk's avatar
hjk committed
436
437
    FakeVimHandler *q;
    Mode m_mode;
438
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
439
    SubMode m_submode;
hjk's avatar
hjk committed
440
441
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
442
443
    QString m_input;
    QTextCursor m_tc;
444
    int m_oldPosition; // copy from last event to check for external changes
445
    int m_anchor;
446
    static QHash<int, Register> m_registers;
hjk's avatar
hjk committed
447
    int m_register;
hjk's avatar
hjk committed
448
449
    QString m_mvcount;
    QString m_opcount;
450
451
    MoveType m_movetype;
    RangeMode m_rangemode;
452
    int m_visualInsertCount;
hjk's avatar
hjk committed
453
454

    bool m_fakeEnd;
hjk's avatar
hjk committed
455
456
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
457

458
459
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
460
    int m_gflag;  // whether current command started with 'g'
461

hjk's avatar
hjk committed
462
    QString m_commandBuffer;
463
464
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
465

466
    bool m_lastSearchForward;
467
    bool m_findPending;
hjk's avatar
hjk committed
468
    QString m_lastInsertion;
hjk's avatar
hjk committed
469

hjk's avatar
hjk committed
470
471
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
472

hjk's avatar
hjk committed
473
474
    typedef void (FakeVimHandler::Private::*Transformation)(int, QTextCursor *);
    void transformText(const Range &range, Transformation transformation);
475

476
    void removeSelectedText(bool exclusive = false);
477
    void removeText(const Range &range);
478
479
480
481
482
483
484
485
486
487
    void removeTransform(int, QTextCursor *);

    void invertCaseSelectedText();
    void invertCaseTransform(int, QTextCursor *);

    void upCaseSelectedText();
    void upCaseTransform(int, QTextCursor *);

    void downCaseSelectedText();
    void downCaseTransform(int, QTextCursor *);
488

489
490
491
492
    QChar m_replacingCharacter;
    void replaceSelectedText(); // replace each character with m_replacingCharacter
    void replaceTransform(int, QTextCursor *);

493
494
495
496
497
498
499
    QString selectedText() const { return text(Range(position(), anchor())); }
    QString text(const Range &range) const;

    void yankSelectedText();
    void yankText(const Range &range, int toregister = '"');

    void pasteText(bool afterCursor);
500

501
    // undo handling
502
503
    void undo();
    void redo();
504
    void setUndoPosition(int pos);
505
    QMap<int, int> m_undoCursorPosition; // revision -> position
506
    bool m_beginEditBlock;
507

hjk's avatar
hjk committed
508
    // extra data for '.'
hjk's avatar
hjk committed
509
    void replay(const QString &text, int count);
510
511
    void setDotCommand(const QString &cmd) { m_dotCommand = cmd; }
    void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); }
hjk's avatar
hjk committed
512
    QString m_dotCommand;
hjk's avatar
hjk committed
513
    bool m_inReplay; // true if we are executing a '.'
hjk's avatar
hjk committed
514

hjk's avatar
hjk committed
515
516
517
518
519
    // extra data for ';'
    QString m_semicolonCount;
    int m_semicolonType;  // 'f', 'F', 't', 'T'
    int m_semicolonKey;

hjk's avatar
hjk committed
520
    // history for '/'
hjk's avatar
hjk committed
521
    QString lastSearchString() const;
522
    static QStringList m_searchHistory;
hjk's avatar
hjk committed
523
524
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
525
    // history for ':'
526
    static QStringList m_commandHistory;
hjk's avatar
hjk committed
527
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
528

hjk's avatar
hjk committed
529
    // visual line mode
530
531
532
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
533

534
535
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
536
    QString m_oldNeedle;
537

hjk's avatar
hjk committed
538
    // vi style configuration
hjk's avatar
hjk committed
539
540
541
542
    QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
    bool hasConfig(int code) const { return config(code).toBool(); }
    bool hasConfig(int code, const char *value) const // FIXME
        { return config(code).toString().contains(value); }
543

544
545
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
546

547
    int m_cursorWidth;
548

hjk's avatar
hjk committed
549
    // auto-indent
550
    QString tabExpand(int len) const;
551
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
552
553
554
555
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
556
    void handleStartOfLine();
hjk's avatar
hjk committed
557

558
    void recordJump();
559
    void recordNewUndo();
560
561
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
562
563

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
564
565
};

566
567
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;
568
QHash<int, Register> FakeVimHandler::Private::m_registers;
569

570
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
571
572
{
    q = parent;
573
574
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
hjk's avatar
hjk committed
575
576
    init();
}
577

hjk's avatar
hjk committed
578
579
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
580
    m_mode = CommandMode;
hjk's avatar
hjk committed
581
582
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
583
    m_passing = false;
584
    m_findPending = false;
hjk's avatar
hjk committed
585
    m_fakeEnd = false;
586
    m_positionPastEnd = m_anchorPastEnd = false;
587
    m_lastSearchForward = true;
hjk's avatar
hjk committed
588
    m_register = '"';
hjk's avatar
hjk committed
589
    m_gflag = false;
590
    m_visualMode = NoVisualMode;
591
    m_targetColumn = 0;
592
    m_visualTargetColumn = 0;
593
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
594
    m_anchor = 0;
595
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
596
    m_inReplay = false;
hjk's avatar
hjk committed
597
    m_justAutoIndented = 0;
598
    m_rangemode = RangeCharMode;
599
    m_beginEditBlock = true;
hjk's avatar
hjk committed
600
601
}

602
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
603
{
604
605
606
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
607

608
    if (key == Key_Escape) {
hjk's avatar
hjk committed
609
        // Not sure this feels good. People often hit Esc several times
610
611
612
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
613
614
615
    }

    // We are interested in overriding  most Ctrl key combinations
616
617
618
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
619
        // Ctrl-K is special as it is the Core's default notion of Locator
620
621
622
623
624
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
625
        }
626
627
628
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
629
630
    }

631
632
633
634
635
636
637
638
639
640
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
    int key = ev->key();
    const int um = key; // keep unmodified key around
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
641
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
642
643
644
645
646
647
648
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
649
        passShortcuts(false);
650
651
652
653
654
655
656
657
658
659
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
        //if (key == ',') { // use ',,' to leave, too.
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
660
661

    // Fake "End of line"
662
663
    m_tc = EDITOR(textCursor());

664
665
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
666
        setTargetColumn();
667
668
669
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
670
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
671
672
673
                Range range(m_oldPosition, m_tc.position());
                m_lastInsertion.append(text(range));
            }
674
675
676
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
677
678
        }
    }
679
680

    m_tc.setVisualNavigation(true);
con's avatar
con committed
681

hjk's avatar
hjk committed
682
    if (m_fakeEnd)
hjk's avatar
hjk committed
683
        moveRight();
hjk's avatar
hjk committed
684

685
    if ((mods & Qt::ControlModifier) != 0) {
686
        if (key >= Key_A && key <= Key_Z)
687
688
            key = shift(key); // make it lower case
        key = control(key);
689
    } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
690
        key = shift(key);
691
    }
692

693
694
695
    QTC_ASSERT(!(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
               qDebug() << "Cursor at EOL before key handler");

hjk's avatar
hjk committed
696
697
698
699
    //if (m_mode == InsertMode)
    //    joinPreviousEditBlock();
    //else
    //    beginEditBlock();
700
    EventResult result = handleKey(key, um, ev->text());
hjk's avatar
hjk committed
701
    //endEditBlock();
hjk's avatar
hjk committed
702

703
704
705
706
    // the command might have destroyed the editor
    if (m_textedit || m_plaintextedit) {
        // We fake vi-style end-of-line behaviour
        m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
hjk's avatar
hjk committed
707

708
709
        QTC_ASSERT(!(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
                   qDebug() << "Cursor at EOL after key handler");
710

711
712
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
713

714
715
716
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
717
    return result;
hjk's avatar
hjk committed
718
719
}

720
721
722
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
hjk's avatar
hjk committed
723
    updateEditor();
724
725
}

726
727
728
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
729
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
730
731
732
733
734
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
735
    m_wasReadOnly = EDITOR(isReadOnly());
736
    //EDITOR(setReadOnly(true));
737

hjk's avatar
hjk committed
738
739
    updateEditor();

740
741
742
743
744
745
746
747
748
749
750
751
752
753
    QTextCursor tc = EDITOR(textCursor());
    if (tc.hasSelection()) {
        int pos = tc.position();
        int anc = tc.anchor();
        m_marks['<'] = anc;
        m_marks['>'] = pos;
        m_anchor = anc;
        m_visualMode = VisualCharMode;
        tc.clearSelection();
        EDITOR(setTextCursor(tc));
        m_tc = tc; // needed in updateSelection
        updateSelection();
    }

754
    //showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
755
756
757
    updateMiniBuffer();
}

hjk's avatar
hjk committed
758
759
760
761
762
763
void FakeVimHandler::Private::updateEditor()
{
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
}

764
765
766
767
void FakeVimHandler::Private::restoreWidget()
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
768
    //EDITOR(removeEventFilter(q));
769
    EDITOR(setReadOnly(m_wasReadOnly));
770
771
    EDITOR(setCursorWidth(m_cursorWidth));
    EDITOR(setOverwriteMode(false));
772

773
    if (isVisualLineMode()) {
774
775
776
777
778
779
        m_tc = EDITOR(textCursor());
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
        m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor);
        m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor);
        EDITOR(setTextCursor(m_tc));
780
    } else if (isVisualCharMode()) {
781
782
783
784
785
786
787
788
        m_tc = EDITOR(textCursor());
        m_tc.setPosition(m_marks['<'], MoveAnchor);
        m_tc.setPosition(m_marks['>'], KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    }

    m_visualMode = NoVisualMode;
    updateSelection();
789
790
}

791
792
EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
793
{
794
    setUndoPosition(m_tc.position());
hjk's avatar
hjk committed
795
    if (m_mode == InsertMode)
796
        return handleInsertMode(key, unmodified, text);
797
    if (m_mode == CommandMode)
798
        return handleCommandMode(key, unmodified, text);
799
    if (m_mode == ExMode || m_mode == SearchForwardMode
800
            || m_mode == SearchBackwardMode)
801
        return handleMiniBufferModes(key, unmodified, text);
802
    return EventUnhandled;
hjk's avatar
hjk committed
803
804
}

805
806
807
808
809
810
811
812
813
814
void FakeVimHandler::Private::stopIncrementalFind()
{
    if (m_findPending) {
        m_findPending = false;
        QTextCursor tc = EDITOR(textCursor());
        tc.setPosition(tc.selectionStart());
        EDITOR(setTextCursor(tc));
    }
}

815
816
817
818
819
820
void FakeVimHandler::Private::setUndoPosition(int pos)
{
    //qDebug() << " CURSOR POS: " << m_undoCursorPosition;
    m_undoCursorPosition[m_tc.document()->availableUndoSteps()] = pos;
}

hjk's avatar
hjk committed
821
822
void FakeVimHandler::Private::moveDown(int n)
{
hjk's avatar
hjk committed
823
824
825
826
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(Down, MoveAnchor, n);
#else
hjk's avatar
hjk committed
827
    const int col = m_tc.position() - m_tc.block().position();
828
829
830
    const int lastLine = m_tc.document()->lastBlock().blockNumber();
    const int targetLine = qMax(0, qMin(lastLine, m_tc.block().blockNumber() + n));
    const QTextBlock &block = m_tc.document()->findBlockByNumber(targetLine);
hjk's avatar
hjk committed
831
    const int pos = block.position();
832
    setPosition(pos + qMax(0, qMin(block.length() - 2, col)));
hjk's avatar
hjk committed
833
    moveToTargetColumn();
hjk's avatar
hjk committed
834
#endif
hjk's avatar
hjk committed
835
836
837
838
}

void FakeVimHandler::Private::moveToEndOfLine()
{
hjk's avatar
hjk committed
839
840
841
842
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(EndOfLine, MoveAnchor);
#else
hjk's avatar
hjk committed
843
    const QTextBlock &block = m_tc.block();
844
845
    const int pos = block.position() + block.length() - 2;
    setPosition(qMax(block.position(), pos));
hjk's avatar
hjk committed
846
#endif
hjk's avatar
hjk committed
847
848
}

hjk's avatar
hjk committed
849
850
851
void FakeVimHandler::Private::moveBehindEndOfLine()
{
    const QTextBlock &block = m_tc.block();
852
    int pos = qMin(block.position() + block.length() - 1, lastPositionInDocument());
hjk's avatar
hjk committed
853
854
855
    setPosition(pos);
}

856
857
858
859
860
861
862
863
864
865
866
void FakeVimHandler::Private::moveToStartOfLine()
{
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(StartOfLine, MoveAnchor);
#else
    const QTextBlock &block = m_tc.block();
    setPosition(block.position());
#endif
}

867
868
869
870
871
void FakeVimHandler::Private::finishMovement(const QString &dotCommand, int count)
{
    finishMovement(dotCommand.arg(count));
}

hjk's avatar
hjk committed
872
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
873
{
874
    //qDebug() << "ANCHOR: " << position() << anchor();
hjk's avatar
hjk committed
875
    if (m_submode == FilterSubMode) {
hjk's avatar
hjk committed
876
877
        int beginLine = lineForPosition(anchor());
        int endLine = lineForPosition(position());
878
        setPosition(qMin(anchor(), position()));
879
        enterExMode();
hjk's avatar
hjk committed
880
        m_currentMessage.clear();
hjk's avatar
hjk committed
881
882
883
884
885
886
887
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

888
    if (isVisualMode())
889
890
        m_marks['>'] = m_tc.position();

891
892
893
894
895
896
    if (m_submode == ChangeSubMode
        || m_submode == DeleteSubMode
        || m_submode == YankSubMode
        || m_submode == TransformSubMode) {
        if (m_submode != YankSubMode)
            beginEditBlock();
897
        if (m_movetype == MoveLineWise)
898
899
900
901
902
903
904
905
906
907
908
909
910
            m_rangemode = (m_submode == ChangeSubMode)
                ? RangeLineModeExclusive
                : RangeLineMode;

        if (m_movetype == MoveInclusive) {
            if (anchor() <= position()) {
                if ( !m_tc.atBlockEnd())
                    moveRight(); // correction
            } else {
                m_anchor++;
            }
        }

911
912
913
914
915
916
917
918
919
        if (m_positionPastEnd) {
            moveBehindEndOfLine();
            moveRight();
        }

        if (m_anchorPastEnd) {
            m_anchor++;
        }

920
921
922
923
        if (m_submode != TransformSubMode) {
            yankSelectedText();
            if (m_movetype == MoveLineWise)
                m_registers[m_register].rangemode = RangeLineMode;
924
        }
925
926

        m_positionPastEnd = m_anchorPastEnd = false;
927
928
929
930
    }

    if (m_submode == ChangeSubMode) {
        removeSelectedText(true);
hjk's avatar
hjk committed
931
        if (!dotCommand.isEmpty())
932
            setDotCommand(QLatin1Char('c') + dotCommand);
933
934
935
        if (m_movetype == MoveLineWise) {
            insertAutomaticIndentation(true);
        }