fakevimhandler.cpp 136 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
#include <QtGui/QInputMethodEvent>
hjk's avatar
hjk committed
76
77
78
79
80
81
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
82
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
83
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
84

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

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

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

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

namespace FakeVim {
namespace Internal {

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

hjk's avatar
hjk committed
114
115
116
117
118
119
120
121
122
123
#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
124

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

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

using namespace Qt;

131

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

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

hjk's avatar
hjk committed
159
160
enum SubSubMode
{
hjk's avatar
hjk committed
161
162
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
163
    NoSubSubMode,
164
165
166
167
    FtSubSubMode,         // used for f, F, t, T
    MarkSubSubMode,       // used for m
    BackTickSubSubMode,   // used for `
    TickSubSubMode,       // used for '
168
    InvertCaseSubSubMode, // used for ~
169
170
171
172
    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
173
174
};

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

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

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

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

206
struct Column
207
{
208
209
210
    Column(int p, int l) : physical(p), logical(l) {}
    int physical; // number of characters in the data
    int logical; // column on screen
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
237
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
238
    {}
239

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

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

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

hjk's avatar
hjk committed
258
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
259
{
hjk's avatar
hjk committed
260
261
262
263
264
265
266
267
268
    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
269
270
}

271
272
273
274
275
276
277
278
279
280
281
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;
}

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

hjk's avatar
hjk committed
287
class Input
hjk's avatar
hjk committed
288
{
hjk's avatar
hjk committed
289
public:
290
    Input() : key(0), unmodified(0), modifiers(0) {}
291
    Input(QChar x) : key(x.unicode()), unmodified(0), modifiers(0), text(x) {}
292
293
294
    Input(int k, int u, int m, QString t)
        : key(k), unmodified(u), modifiers(m), text(t)
    {}
hjk's avatar
hjk committed
295
296
297

    int key;
    int unmodified;
298
    int modifiers;
hjk's avatar
hjk committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
    QString text;
};

bool operator==(const Input &a, const Input &b)
{
    return a.key == b.key && a.unmodified == b.unmodified && a.text == b.text;
}

typedef QVector<Input> Inputs;

// Mappings for a specific mode.
class ModeMapping : private QList<QPair<Inputs, Inputs> >
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
317
318
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
    }

    void insert(const Inputs &from, const Inputs &to)
    {
        for (int i = 0; i != size(); ++i)
            if (at(i).first == from) {
                (*this)[i].second = to;
                return;
            }
        append(QPair<Inputs, Inputs>(from, to));
    }

    void remove(const Inputs &from)
    {
        for (int i = 0; i != size(); ++i)
            if (at(i).first == from) {
                removeAt(i);
                return;
            }
    }

    // Returns 'false' if more input input is needed to decide whether a 
    // mapping needs to be applied. If a decision can be made, return 'true',
    // and replace *input with the mapped data.
    bool mappingDone(Inputs *input) const
    {
        Q_UNUSED(input);
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
            // A mapping 
            if (startsWith(at(i).first, *input)) {
                if (at(i).first.size() != input->size())
                    return false; // This can be extended.
                // Actual mapping.
                *input = at(i).second;
                return true;
            }
        }
        // No extensible mapping found. Use input as-is.
        return true;
    }

private:
    static bool startsWith(const Inputs &haystack, const Inputs &needle)
    {
        // Input is already too long.
        if (needle.size() > haystack.size())
            return false;
        for (int i = 0; i != needle.size(); ++i) {
368
            if (needle.at(i).text != haystack.at(i).text)
hjk's avatar
hjk committed
369
370
371
372
373
374
375
376
377
378
                return false;
        }
        return true;
    }
};

class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
379
public:
380
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
381

382
383
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
384
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
385
    void handleExCommand(const QString &cmd);
386
387
388

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

390
    void installEventFilter();
391
    void passShortcuts(bool enable);
392
    void setupWidget();
393
    void restoreWidget(int tabSize);
394

395
    friend class FakeVimHandler;
hjk's avatar
hjk committed
396
397
398
399
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
hjk's avatar
hjk committed
400
401
402
403
404
405
406
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
    EventResult handleMiniBufferModes(const Input &);
    EventResult handleCommandSubSubMode(const Input &);
407
408
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
409
    void resetCommandMode();
hjk's avatar
hjk committed
410
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
411
    void highlightMatches(const QString &needle);
412
    void stopIncrementalFind();
hjk's avatar
hjk committed
413

hjk's avatar
hjk committed
414
415
416
    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
417
418
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
419
420
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
421

hjk's avatar
hjk committed
422
    int lastPositionInDocument() const; // last valid pos in doc
423
424
    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
425
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
426
427
    QString lineContents(int line) const; // 1 based line
    void setLineContents(int line, const QString &contents) const; // 1 based line
hjk's avatar
hjk committed
428

hjk's avatar
hjk committed
429
430
    int linesOnScreen() const;
    int columnsOnScreen() const;
431
432
433
434
    int linesInDocument() const;

    // all zero-based counting
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
435
    int cursorLineInDocument() const;
436
437
438
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
439
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
440
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
441
442
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
443

444
445
446
447
448
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

449
    // helper functions for indenting
450
    bool isElectricCharacter(QChar c) const;
451
452
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
453
454
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
455

hjk's avatar
hjk committed
456
    void moveToFirstNonBlankOnLine();
457
    void moveToTargetColumn();
458
459
    void setTargetColumn() {
        m_targetColumn = leftDist();
460
        m_visualTargetColumn = m_targetColumn;
461
462
        //qDebug() << "TARGET: " << m_targetColumn;
    }
463
    void moveToNextWord(bool simple, bool deleteWord = false);
464
    void moveToMatchingParanthesis();
465
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
466
467

    // to reduce line noise
hjk's avatar
hjk committed
468
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
469
    void moveToStartOfLine();
hjk's avatar
hjk committed
470
    void moveToEndOfLine();
hjk's avatar
hjk committed
471
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
472
473
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
474
475
    void moveRight(int n = 1) { m_tc.movePosition(Right, MoveAnchor, n); }
    void moveLeft(int n = 1) { m_tc.movePosition(Left, MoveAnchor, n); }
476
477
    void setAnchor() { if (!isVisualMode()) m_anchor = m_tc.position(); }
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
478
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
479

480
    bool handleFfTt(int key);
hjk's avatar
hjk committed
481

hjk's avatar
hjk committed
482
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
483
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
484
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
485

486
    void enterInsertMode();
487
    void enterCommandMode();
488
    void enterExMode();
hjk's avatar
hjk committed
489
490
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
491
    void notImplementedYet();
492
493
    void updateMiniBuffer();
    void updateSelection();
494
    QWidget *editor() const;
495
496
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
497
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
498
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
499
500
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
501

502
503
504
    // 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);
505
506
507
    // 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);
508

509
510
511
512
513
    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
514
    void updateEditor();
515

516
517
518
519
520
521
522
    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);

523
524
525
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
526
    bool m_wasReadOnly; // saves read-only state of document
527

hjk's avatar
hjk committed
528
529
    FakeVimHandler *q;
    Mode m_mode;
530
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
531
    SubMode m_submode;
hjk's avatar
hjk committed
532
533
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
534
    QTextCursor m_tc;
535
    int m_oldPosition; // copy from last event to check for external changes
536
    int m_anchor;
537
    static QHash<int, Register> m_registers;
hjk's avatar
hjk committed
538
    int m_register;
hjk's avatar
hjk committed
539
540
    QString m_mvcount;
    QString m_opcount;
541
542
    MoveType m_movetype;
    RangeMode m_rangemode;
543
    int m_visualInsertCount;
hjk's avatar
hjk committed
544
545

    bool m_fakeEnd;
hjk's avatar
hjk committed
546
547
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
548

549
550
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
551
    int m_gflag;  // whether current command started with 'g'
552

hjk's avatar
hjk committed
553
    QString m_commandBuffer;
554
555
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
556

557
    bool m_lastSearchForward;
558
    bool m_findPending;
hjk's avatar
hjk committed
559
    QString m_lastInsertion;
hjk's avatar
hjk committed
560

hjk's avatar
hjk committed
561
562
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
563

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

567
    void removeSelectedText(bool exclusive = false);
568
    void removeText(const Range &range);
569
570
571
572
573
574
575
576
577
578
    void removeTransform(int, QTextCursor *);

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

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

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

580
581
582
583
    QChar m_replacingCharacter;
    void replaceSelectedText(); // replace each character with m_replacingCharacter
    void replaceTransform(int, QTextCursor *);

584
585
586
587
588
589
590
    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);
591

592
    // undo handling
593
594
    void undo();
    void redo();
595
    void setUndoPosition(int pos);
596
    QMap<int, int> m_undoCursorPosition; // revision -> position
597
    bool m_beginEditBlock;
598

hjk's avatar
hjk committed
599
    // extra data for '.'
hjk's avatar
hjk committed
600
    void replay(const QString &text, int count);
601
602
    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
603
    QString m_dotCommand;
hjk's avatar
hjk committed
604
    bool m_inReplay; // true if we are executing a '.'
hjk's avatar
hjk committed
605

hjk's avatar
hjk committed
606
607
608
609
610
    // extra data for ';'
    QString m_semicolonCount;
    int m_semicolonType;  // 'f', 'F', 't', 'T'
    int m_semicolonKey;

hjk's avatar
hjk committed
611
    // history for '/'
hjk's avatar
hjk committed
612
    QString lastSearchString() const;
613
    static QStringList m_searchHistory;
hjk's avatar
hjk committed
614
615
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
616
    // history for ':'
617
    static QStringList m_commandHistory;
hjk's avatar
hjk committed
618
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
619

hjk's avatar
hjk committed
620
    // visual line mode
621
622
623
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
624

625
626
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
627
    QString m_oldNeedle;
628

hjk's avatar
hjk committed
629
    // vi style configuration
hjk's avatar
hjk committed
630
631
632
633
    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); }
634

635
636
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
637

638
    int m_cursorWidth;
639

hjk's avatar
hjk committed
640
    // auto-indent
641
    QString tabExpand(int len) const;
642
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
643
644
645
646
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
647
    void handleStartOfLine();
hjk's avatar
hjk committed
648

649
    void recordJump();
650
    void recordNewUndo();
651
652
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
653
654

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
655

656
657
658
659
660
661
662
663
664
665
666
667
668
669
    QString extractCommand(const QString &line, int *beginLine, int *endLine);
    bool handleExGotoCommand(const QString &line);
    bool handleExReadCommand(const QString &line);
    bool handleExWriteCommand(const QString &line);
    bool handleExRedoCommand(const QString &line);
    bool handleExShiftRightCommand(const QString &line);
    bool handleExBangCommand(const QString &line);
    bool handleExNormalCommand(const QString &line);
    bool handleExDeleteCommand(const QString &line);
    bool handleExSetCommand(const QString &line);
    bool handleExHistoryCommand(const QString &line);
    bool handleExSubstituteCommand(const QString &line);
    bool handleExMapCommand(const QString &line);

hjk's avatar
hjk committed
670
    // All mappings.
hjk's avatar
hjk committed
671
    typedef QHash<char, ModeMapping> Mappings;
hjk's avatar
hjk committed
672
    Mappings m_mappings;
hjk's avatar
hjk committed
673
674
675
676
677

    QVector<Input> m_pendingInput;

    void timerEvent(QTimerEvent *ev);
    int m_inputTimer;
hjk's avatar
hjk committed
678
679
};

680
681
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;
682
QHash<int, Register> FakeVimHandler::Private::m_registers;
683

684
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
685
686
{
    q = parent;
687
688
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
hjk's avatar
hjk committed
689
690
    init();
}
691

hjk's avatar
hjk committed
692
693
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
694
    m_mode = CommandMode;
hjk's avatar
hjk committed
695
696
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
697
    m_passing = false;
698
    m_findPending = false;
hjk's avatar
hjk committed
699
    m_fakeEnd = false;
700
    m_positionPastEnd = m_anchorPastEnd = false;
701
    m_lastSearchForward = true;
hjk's avatar
hjk committed
702
    m_register = '"';
hjk's avatar
hjk committed
703
    m_gflag = false;
704
    m_visualMode = NoVisualMode;
705
    m_targetColumn = 0;
706
    m_visualTargetColumn = 0;
707
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
708
    m_anchor = 0;
709
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
710
    m_inReplay = false;
hjk's avatar
hjk committed
711
    m_justAutoIndented = 0;
712
    m_rangemode = RangeCharMode;
713
    m_beginEditBlock = true;
hjk's avatar
hjk committed
714
    m_inputTimer = -1;
hjk's avatar
hjk committed
715
716
}

717
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
718
{
719
720
721
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
722

723
    if (key == Key_Escape) {
hjk's avatar
hjk committed
724
        // Not sure this feels good. People often hit Esc several times
725
726
727
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
728
729
730
    }

    // We are interested in overriding  most Ctrl key combinations
731
732
733
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
734
        // Ctrl-K is special as it is the Core's default notion of Locator
735
736
737
738
739
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
740
        }
741
742
743
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
744
745
    }

746
747
748
749
750
751
752
753
754
755
    // 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
756
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
757
758
759
760
761
762
763
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
764
        passShortcuts(false);
765
766
767
768
769
770
771
772
773
774
        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
775
776

    // Fake "End of line"
777
778
    m_tc = EDITOR(textCursor());

779
780
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
781
        setTargetColumn();
782
783
784
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
785
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
786
787
788
                Range range(m_oldPosition, m_tc.position());
                m_lastInsertion.append(text(range));
            }
789
790
791
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
792
793
        }
    }
794
795

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

hjk's avatar
hjk committed
797
    if (m_fakeEnd)
hjk's avatar
hjk committed
798
        moveRight();
hjk's avatar
hjk committed
799

800
    if ((mods & Qt::ControlModifier) != 0) {
801
        if (key >= Key_A && key <= Key_Z)
802
803
            key = shift(key); // make it lower case
        key = control(key);
804
    } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
805
        key = shift(key);
806
    }
807

hjk's avatar
hjk committed
808
809
810
    QTC_ASSERT(
        !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
        qDebug() << "Cursor at EOL before key handler");
811

812
    EventResult result = handleKey(Input(key, um, mods, ev->text()));
hjk's avatar
hjk committed
813

814
815
816
817
    // 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
818

hjk's avatar
hjk committed
819
820
821
        QTC_ASSERT(
            !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
            qDebug() << "Cursor at EOL after key handler");
822

823
824
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
825

826
827
828
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
829
    return result;
hjk's avatar
hjk committed
830
831
}

832
833
834
835
836
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

837
838
839
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
840
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
841
842
843
844
845
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
846
    m_wasReadOnly = EDITOR(isReadOnly());
847
    //EDITOR(setReadOnly(true));
848

hjk's avatar
hjk committed
849
850
    updateEditor();

851
852
853
854
855
856
857
858
859
860
861
862
863
864
    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();
    }

865
866
867
    updateMiniBuffer();
}

hjk's avatar
hjk committed
868
869
870
871
872
873
void FakeVimHandler::Private::updateEditor()
{
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
}

874
void FakeVimHandler::Private::restoreWidget(int tabSize)
875
876
877
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
878
    //EDITOR(removeEventFilter(q));
879
    EDITOR(setReadOnly(m_wasReadOnly));
880
881
    EDITOR(setCursorWidth(m_cursorWidth));
    EDITOR(setOverwriteMode(false));
882
883
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * tabSize));
884

885
    if (isVisualLineMode()) {
886
887
888
889
890
891
        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));
892
    } else if (isVisualCharMode()) {
893
894
895
896
897
898
899
900
        m_tc = EDITOR(textCursor());
        m_tc.setPosition(m_marks['<'], MoveAnchor);
        m_tc.setPosition(m_marks['>'], KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    }

    m_visualMode = NoVisualMode;
    updateSelection();
901
902
}

hjk's avatar
hjk committed
903
EventResult FakeVimHandler::Private::handleKey(const Input &input)
hjk's avatar
hjk committed
904
{
hjk's avatar
hjk committed
905
906
907
908
909
910
911
912
913
914
    if (m_mode == InsertMode || m_mode == CommandMode) {
        m_pendingInput.append(input);
        const char code = m_mode == InsertMode ? 'i' : 'n';
        if (m_mappings[code].mappingDone(&m_pendingInput))
            return handleKey2();
        if (m_inputTimer != -1)
            killTimer(m_inputTimer);
        m_inputTimer = startTimer(1000);
        return EventHandled;
    }
915
    if (m_mode == ExMode || m_mode == SearchForwardMode
916
            || m_mode == SearchBackwardMode)
hjk's avatar
hjk committed
917
        return handleMiniBufferModes(input);
918
    return EventUnhandled;
hjk's avatar
hjk committed
919
920
}

hjk's avatar
hjk committed
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
EventResult FakeVimHandler::Private::handleKey2()
{
    setUndoPosition(m_tc.position());
    if (m_mode == InsertMode) {
        EventResult result = EventUnhandled;
        foreach (const Input &in, m_pendingInput) {
            EventResult r = handleInsertMode(in);
            if (r == EventHandled)
                result = EventHandled;
        }
        m_pendingInput.clear();
        return result;
    }
    if (m_mode == CommandMode) {
        EventResult result = EventUnhandled;
        foreach (const Input &in, m_pendingInput) {
            EventResult r = handleCommandMode(in);
            if (r == EventHandled)
                result = EventHandled;
        }
        m_pendingInput.clear();
        return result;
    }
    return EventUnhandled;
}

void FakeVimHandler::Private::timerEvent(QTimerEvent *ev)
{
    Q_UNUSED(ev);
    handleKey2();
}

953
954
955
956
957
958
959
960
961
962
void FakeVimHandler::Private::stopIncrementalFind()
{
    if (m_findPending) {
        m_findPending = false;
        QTextCursor tc = EDITOR(textCursor());
        tc.setPosition(tc.selectionStart());
        EDITOR(setTextCursor(tc));
    }
}

963
964
965
966
967
968
void FakeVimHandler::Private::setUndoPosition(int pos)
{
    //qDebug() << " CURSOR POS: " << m_undoCursorPosition;
    m_undoCursorPosition[m_tc.document()->availableUndoSteps()] = pos;
}

hjk's avatar
hjk committed
969
970
void FakeVimHandler::Private::moveDown(int n)
{
hjk's avatar
hjk committed
971
972
973
974
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(Down, MoveAnchor, n);
#else
hjk's avatar
hjk committed
975
    const int col = m_tc.position() - m_tc.block().position();
976
977
978
    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
979
    const int pos = block.position();
980
    setPosition(pos + qMax(0, qMin(block.length() - 2, col)));
hjk's avatar
hjk committed
981
    moveToTargetColumn();
hjk's avatar
hjk committed
982
#endif
hjk's avatar
hjk committed
983
984
985
986
}

void FakeVimHandler::Private::moveToEndOfLine()