fakevimhandler.cpp 140 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
//
31
32
33
34
35
36
37
38
39
40
41
// 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:
42
//
43
44
45
//   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.
46
//
47
48
49
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
50
51
52
53
//   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().
//
54
//   There is always a "current" cursor (m_tc). A current "region of interest"
55
56
57
//   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).
58
//   The value of m_tc.anchor() is not used.
59

60
61
62
#include "fakevimhandler.h"
#include "fakevimsyntax.h"

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

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

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

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

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

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

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

namespace FakeVim {
namespace Internal {

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

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

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

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

using namespace Qt;

132

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

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

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

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

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

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

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

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

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

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

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

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

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

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

hjk's avatar
hjk committed
288
class Input
hjk's avatar
hjk committed
289
{
hjk's avatar
hjk committed
290
public:
291
292
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
293

294
295
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
296

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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
    Input(int k, int m, QString t)
        : m_key(k), m_modifiers(m), m_text(t)
    {
        // m_xkey is only a cache.
        m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
    }

    bool isDigit() const
    {
        return m_xkey >= '0' && m_xkey <= '9';
    }
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

    bool is(int c) const
    {
        return m_xkey == c && (m_modifiers == 0 || m_modifiers == Qt::ShiftModifier);
    }

    bool isControl(int c) const
    {
        return m_modifiers == Qt::ControlModifier &&
            (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
    }

    bool isShift(int c) const
    {
        return m_modifiers == Qt::ShiftModifier && m_xkey == c;
    }

    bool operator==(const Input &a) const
    {
        return a.m_key == m_key && m_text == a.m_text;
    }

    QString text() const { return m_text; }

    int key() const { return m_key; }

private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
344
345
346
347
348
349
350
351
352
353
354

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
355
356
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
    }

    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;
            }
    }

378
    // Returns 'false' if more input input is needed to decide whether a
hjk's avatar
hjk committed
379
380
381
382
383
384
385
    // 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) {
386
            // A mapping
hjk's avatar
hjk committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
            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) {
406
            if (needle.at(i).text() != haystack.at(i).text())
hjk's avatar
hjk committed
407
408
409
410
411
412
413
414
415
416
                return false;
        }
        return true;
    }
};

class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
417
public:
418
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
419

420
421
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
422
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
423
    void handleExCommand(const QString &cmd);
424
425
426

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

428
    void installEventFilter();
429
    void passShortcuts(bool enable);
430
    void setupWidget();
431
    void restoreWidget(int tabSize);
432

433
    friend class FakeVimHandler;
hjk's avatar
hjk committed
434
435

    void init();
hjk's avatar
hjk committed
436
437
438
439
440
441
442
    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 &);
443
444
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
445
    void resetCommandMode();
hjk's avatar
hjk committed
446
    void search(const QString &needle, bool forward, bool incSearch = false);
hjk's avatar
hjk committed
447
    void highlightMatches(const QString &needle);
448
    void stopIncrementalFind();
hjk's avatar
hjk committed
449

hjk's avatar
hjk committed
450
451
452
    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
453
454
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
455
456
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
457

hjk's avatar
hjk committed
458
    int lastPositionInDocument() const; // last valid pos in doc
459
460
    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
461
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
462
463
    QString lineContents(int line) const; // 1 based line
    void setLineContents(int line, const QString &contents) const; // 1 based line
hjk's avatar
hjk committed
464

hjk's avatar
hjk committed
465
466
    int linesOnScreen() const;
    int columnsOnScreen() const;
467
468
469
470
    int linesInDocument() const;

    // all zero-based counting
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
471
    int cursorLineInDocument() const;
472
473
474
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
475
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
476
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
477
478
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
479

480
481
482
483
484
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

485
    // helper functions for indenting
486
    bool isElectricCharacter(QChar c) const;
487
488
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
489
490
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
491

hjk's avatar
hjk committed
492
    void moveToFirstNonBlankOnLine();
493
    void moveToTargetColumn();
494
495
    void setTargetColumn() {
        m_targetColumn = leftDist();
496
        m_visualTargetColumn = m_targetColumn;
497
498
        //qDebug() << "TARGET: " << m_targetColumn;
    }
499
    void moveToNextWord(bool simple, bool deleteWord = false);
500
    void moveToMatchingParanthesis();
501
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
502
503

    // to reduce line noise
hjk's avatar
hjk committed
504
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
505
    void moveToStartOfLine();
hjk's avatar
hjk committed
506
    void moveToEndOfLine();
hjk's avatar
hjk committed
507
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
508
509
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
510
511
    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
512
    void setAnchor();
513
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
514
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
515

516
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
517

hjk's avatar
hjk committed
518
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
519
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
520
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
521

522
    void enterInsertMode();
523
    void enterCommandMode();
524
    void enterExMode();
hjk's avatar
hjk committed
525
526
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
527
    void notImplementedYet();
528
529
    void updateMiniBuffer();
    void updateSelection();
530
    QWidget *editor() const;
531
532
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
533
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
534
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
535
536
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
537

538
539
540
    // 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);
541
542
543
    // 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);
544

545
546
547
548
549
    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
550
    void updateEditor();
551

552
553
554
555
556
557
558
    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);

559
560
561
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
562
    bool m_wasReadOnly; // saves read-only state of document
563

hjk's avatar
hjk committed
564
565
    FakeVimHandler *q;
    Mode m_mode;
566
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
567
    SubMode m_submode;
hjk's avatar
hjk committed
568
    SubSubMode m_subsubmode;
569
    Input m_subsubdata;
hjk's avatar
hjk committed
570
    QTextCursor m_tc;
571
    int m_oldPosition; // copy from last event to check for external changes
572
    int m_anchor;
573
    static QHash<int, Register> m_registers;
hjk's avatar
hjk committed
574
    int m_register;
hjk's avatar
hjk committed
575
576
    QString m_mvcount;
    QString m_opcount;
577
578
    MoveType m_movetype;
    RangeMode m_rangemode;
579
    int m_visualInsertCount;
hjk's avatar
hjk committed
580
581

    bool m_fakeEnd;
hjk's avatar
hjk committed
582
583
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
584

585
586
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
587
    int m_gflag;  // whether current command started with 'g'
588

hjk's avatar
hjk committed
589
    QString m_commandBuffer;
590
591
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
592

593
    bool m_lastSearchForward;
594
    bool m_findPending;
hjk's avatar
hjk committed
595
    QString m_lastInsertion;
hjk's avatar
hjk committed
596

hjk's avatar
hjk committed
597
598
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
599

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

hjk's avatar
hjk committed
603
    void removeSelectedText();
604
    void removeText(const Range &range);
605
606
607
608
609
610
611
612
613
614
    void removeTransform(int, QTextCursor *);

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

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

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

616
617
618
619
    QChar m_replacingCharacter;
    void replaceSelectedText(); // replace each character with m_replacingCharacter
    void replaceTransform(int, QTextCursor *);

620
621
622
623
624
625
626
    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);
627

628
    // undo handling
629
630
    void undo();
    void redo();
631
    void setUndoPosition(int pos);
632
    QMap<int, int> m_undoCursorPosition; // revision -> position
633
    bool m_beginEditBlock;
634

hjk's avatar
hjk committed
635
    // extra data for '.'
hjk's avatar
hjk committed
636
    void replay(const QString &text, int count);
637
638
    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
639
    QString m_dotCommand;
hjk's avatar
hjk committed
640
    bool m_inReplay; // true if we are executing a '.'
hjk's avatar
hjk committed
641

hjk's avatar
hjk committed
642
643
    // extra data for ';'
    QString m_semicolonCount;
644
645
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
646

hjk's avatar
hjk committed
647
    // history for '/'
hjk's avatar
hjk committed
648
    QString lastSearchString() const;
649
    static QStringList m_searchHistory;
hjk's avatar
hjk committed
650
651
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
652
    // history for ':'
653
    static QStringList m_commandHistory;
hjk's avatar
hjk committed
654
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
655

hjk's avatar
hjk committed
656
    // visual line mode
657
658
659
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
660

661
662
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
663
    QString m_oldNeedle;
664

hjk's avatar
hjk committed
665
    // vi style configuration
hjk's avatar
hjk committed
666
667
668
669
    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); }
670

671
672
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
673

674
    int m_cursorWidth;
675

hjk's avatar
hjk committed
676
    // auto-indent
677
    QString tabExpand(int len) const;
678
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
679
680
681
682
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
683
    void handleStartOfLine();
hjk's avatar
hjk committed
684

685
    void recordJump();
686
    void recordNewUndo();
687
688
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
689
690

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

692
    bool handleExCommandHelper(const QString &cmd); // Returns success.
693
    QString extractCommand(const QString &line, int *beginLine, int *endLine);
694
695
    bool handleExBangCommand(const QString &line);
    bool handleExDeleteCommand(const QString &line);
696
    bool handleExGotoCommand(const QString &line);
697
698
699
    bool handleExHistoryCommand(const QString &line);
    bool handleExMapCommand(const QString &line);
    bool handleExNormalCommand(const QString &line);
700
701
702
    bool handleExReadCommand(const QString &line);
    bool handleExRedoCommand(const QString &line);
    bool handleExSetCommand(const QString &line);
703
704
    bool handleExShiftRightCommand(const QString &line);
    bool handleExSourceCommand(const QString &line);
705
    bool handleExSubstituteCommand(const QString &line);
706
    bool handleExWriteCommand(const QString &line);
707

hjk's avatar
hjk committed
708
    // All mappings.
hjk's avatar
hjk committed
709
    typedef QHash<char, ModeMapping> Mappings;
710
    static Mappings m_mappings;
hjk's avatar
hjk committed
711
712
713
714
715

    QVector<Input> m_pendingInput;

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

718
719
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;
720
QHash<int, Register> FakeVimHandler::Private::m_registers;
721
FakeVimHandler::Private::Mappings FakeVimHandler::Private::m_mappings;
722

723
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
724
{
725
    static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
726
    q = parent;
727
728
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
729
    new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
730
731
    init();
}
732

hjk's avatar
hjk committed
733
734
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
735
    m_mode = CommandMode;
hjk's avatar
hjk committed
736
737
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
738
    m_passing = false;
739
    m_findPending = false;
hjk's avatar
hjk committed
740
    m_fakeEnd = false;
741
    m_positionPastEnd = m_anchorPastEnd = false;
742
    m_lastSearchForward = true;
hjk's avatar
hjk committed
743
    m_register = '"';
hjk's avatar
hjk committed
744
    m_gflag = false;
745
    m_visualMode = NoVisualMode;
746
    m_targetColumn = 0;
747
    m_visualTargetColumn = 0;
748
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
749
    m_anchor = 0;
750
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
751
    m_inReplay = false;
hjk's avatar
hjk committed
752
    m_justAutoIndented = 0;
753
    m_rangemode = RangeCharMode;
754
    m_beginEditBlock = true;
hjk's avatar
hjk committed
755
    m_inputTimer = -1;
hjk's avatar
hjk committed
756
757
}

758
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
759
{
760
761
762
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
763

764
    if (key == Key_Escape) {
hjk's avatar
hjk committed
765
        // Not sure this feels good. People often hit Esc several times
766
767
768
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
769
770
771
    }

    // We are interested in overriding  most Ctrl key combinations
772
773
774
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
775
        // Ctrl-K is special as it is the Core's default notion of Locator
776
777
778
779
780
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
781
        }
782
783
784
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
785
786
    }

787
788
789
790
791
792
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
793
    const int key = ev->key();
794
795
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
796
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
797
798
799
800
801
802
803
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
804
        passShortcuts(false);
805
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
806
        //if (input.is(',')) { // use ',,' to leave, too.
807
808
809
810
811
812
813
814
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
815
816

    // Fake "End of line"
817
818
    m_tc = EDITOR(textCursor());

819
820
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
821
        setTargetColumn();
822
823
824
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
825
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
826
827
828
                Range range(m_oldPosition, m_tc.position());
                m_lastInsertion.append(text(range));
            }
829
830
831
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
832
833
        }
    }
834
835

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

hjk's avatar
hjk committed
837
    if (m_fakeEnd)
hjk's avatar
hjk committed
838
        moveRight();
hjk's avatar
hjk committed
839

840
841
842
843
844
845
846
    //if ((mods & Qt::ControlModifier) != 0) {
    //    if (key >= Key_A && key <= Key_Z)
    //        key = shift(key); // make it lower case
    //    key = control(key);
    //} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
    //    key = shift(key);
    //}
847

hjk's avatar
hjk committed
848
849
850
    QTC_ASSERT(
        !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
        qDebug() << "Cursor at EOL before key handler");
851

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

854
855
856
857
    // 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
858

hjk's avatar
hjk committed
859
860
861
        QTC_ASSERT(
            !(m_mode != InsertMode && m_tc.atBlockEnd() && m_tc.block().length() > 1),
            qDebug() << "Cursor at EOL after key handler");
862

863
864
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
865

866
867
868
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
869
    return result;
hjk's avatar
hjk committed
870
871
}

872
873
874
875
876
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

877
878
879
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
880
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
881
882
883
884
885
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
886
    m_wasReadOnly = EDITOR(isReadOnly());
887
    //EDITOR(setReadOnly(true));
888

hjk's avatar
hjk committed
889
890
    updateEditor();

891
892
893
894
895
896
897
898
899
900
901
902
903
904
    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();
    }

905
906
907
    updateMiniBuffer();
}

hjk's avatar
hjk committed
908
909
910
911
912
913
void FakeVimHandler::Private::updateEditor()
{
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
}

914
void FakeVimHandler::Private::restoreWidget(int tabSize)
915
916
917
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
918
    //EDITOR(removeEventFilter(q));
919
    EDITOR(setReadOnly(m_wasReadOnly));
920
921
    EDITOR(setCursorWidth(m_cursorWidth));
    EDITOR(setOverwriteMode(false));
922
923
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * tabSize));
924

925
    if (isVisualLineMode()) {
926
927
928
929
930
931
        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));
932
    } else if (isVisualCharMode()) {
933
934
935
936
937
938
939
940
        m_tc = EDITOR(textCursor());
        m_tc.setPosition(m_marks['<'], MoveAnchor);
        m_tc.setPosition(m_marks['>'], KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    }

    m_visualMode = NoVisualMode;
    updateSelection();
941
942
}

hjk's avatar
hjk committed
943
EventResult FakeVimHandler::Private::handleKey(const Input &input)
hjk's avatar
hjk committed
944
{
hjk's avatar
hjk committed
945
946
947
948
949
950
951
952
953
954
    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;
    }
955
    if (m_mode == ExMode || m_mode == SearchForwardMode
956
            || m_mode == SearchBackwardMode)
hjk's avatar
hjk committed
957
        return handleMiniBufferModes(input);
958
    return EventUnhandled;
hjk's avatar
hjk committed
959
960
}

hjk's avatar
hjk committed
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
<