fakevimhandler.cpp 138 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
    DownCaseSubSubMode,   // used for gu
    UpCaseSubSubMode,     // used for gU
    ReplaceSubSubMode,    // used for r after visual mode
hjk's avatar
hjk committed
172
    TextObjectSubSubMode, // used for thing like iw, aW, as etc.
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); }
hjk's avatar
hjk committed
476
    void setAnchor();
477
    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

hjk's avatar
hjk committed
567
    void removeSelectedText();
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
    bool handleExCommandHelper(const QString &cmd); // Returns success.
657
    QString extractCommand(const QString &line, int *beginLine, int *endLine);
658
659
    bool handleExBangCommand(const QString &line);
    bool handleExDeleteCommand(const QString &line);
660
    bool handleExGotoCommand(const QString &line);
661
662
663
    bool handleExHistoryCommand(const QString &line);
    bool handleExMapCommand(const QString &line);
    bool handleExNormalCommand(const QString &line);
664
665
666
    bool handleExReadCommand(const QString &line);
    bool handleExRedoCommand(const QString &line);
    bool handleExSetCommand(const QString &line);
667
668
    bool handleExShiftRightCommand(const QString &line);
    bool handleExSourceCommand(const QString &line);
669
    bool handleExSubstituteCommand(const QString &line);
670
    bool handleExWriteCommand(const QString &line);
671

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

    QVector<Input> m_pendingInput;

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

682
683
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;
684
QHash<int, Register> FakeVimHandler::Private::m_registers;
685
FakeVimHandler::Private::Mappings FakeVimHandler::Private::m_mappings;
686

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

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

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

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

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

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

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

    // Fake "End of line"
780
781
    m_tc = EDITOR(textCursor());

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

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

hjk's avatar
hjk committed
800
    if (m_fakeEnd)
hjk's avatar
hjk committed
801
        moveRight();
hjk's avatar
hjk committed
802

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

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

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

817
818
819
820
    // 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
821

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

826
827
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
828

829
830
831
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
832
    return result;
hjk's avatar
hjk committed
833
834
}

835
836
837
838
839
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

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

hjk's avatar
hjk committed
852
853
    updateEditor();

854
855
856
857
858
859
860
861
862
863
864
865
866
867
    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();
    }

868
869
870
    updateMiniBuffer();
}

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

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

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

    m_visualMode = NoVisualMode;
    updateSelection();
904
905
}

hjk's avatar
hjk committed
906
EventResult FakeVimHandler::Private::handleKey(const Input &input)
hjk's avatar
hjk committed
907
{
hjk's avatar
hjk committed
908
909
910
911
912
913
914
915
916
917
    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;
    }
918
    if (m_mode == ExMode || m_mode == SearchForwardMode
919
            || m_mode == SearchBackwardMode)
hjk's avatar
hjk committed
920
        return handleMiniBufferModes(input);
921
    return EventUnhandled;
hjk's avatar
hjk committed
922
923
}

hjk's avatar
hjk committed
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
953
954
955
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();
}

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

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