fakevimhandler.cpp 144 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
enum Mode
{
    InsertMode,
136
    ReplaceMode,
hjk's avatar
hjk committed
137
    CommandMode,
138
    ExMode,
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 "
149
150
    ShiftLeftSubMode,  // used for <
    ShiftRightSubMode, // used for >
151
    TransformSubMode,  // used for ~/gu/gU
hjk's avatar
hjk committed
152
153
    WindowSubMode,     // used for Ctrl-w
    YankSubMode,       // used for y
154
155
    ZSubMode,          // used for z
    CapitalZSubMode    // used for Z
hjk's avatar
hjk committed
156
};
hjk's avatar
hjk committed
157

hjk's avatar
hjk committed
158
159
enum SubSubMode
{
hjk's avatar
hjk committed
160
161
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
162
    NoSubSubMode,
163
164
165
166
    FtSubSubMode,         // used for f, F, t, T
    MarkSubSubMode,       // used for m
    BackTickSubSubMode,   // used for `
    TickSubSubMode,       // used for '
167
    InvertCaseSubSubMode, // used for ~
168
169
170
    DownCaseSubSubMode,   // used for gu
    UpCaseSubSubMode,     // used for gU
    ReplaceSubSubMode,    // used for r after visual mode
hjk's avatar
hjk committed
171
    TextObjectSubSubMode, // used for thing like iw, aW, as etc.
172
    SearchSubSubMode,
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
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
262
263
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
264
265
        if (c.isPrint())
            res += c;
266
267
        else if (cc == '\n')
            res += QLatin1String("<CR>");
hjk's avatar
hjk committed
268
        else
269
            res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
270
271
    }
    return res;
hjk's avatar
hjk committed
272
273
}

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

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

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

296
297
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
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
344
345
    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
346

347
348
349
350
351
QDebug &operator<<(QDebug &ts, const Input &input)
{
    return ts << input.text() << input.key();
}

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

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

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

hjk's avatar
hjk committed
420

hjk's avatar
hjk committed
421
422
423
424
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
425
public:
426
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
427

428
429
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
430
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
431
    void handleExCommand(const QString &cmd);
432
433
434

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

436
    void installEventFilter();
437
    void passShortcuts(bool enable);
438
    void setupWidget();
439
    void restoreWidget(int tabSize);
440

441
    friend class FakeVimHandler;
hjk's avatar
hjk committed
442
443

    void init();
hjk's avatar
hjk committed
444
445
446
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
447
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
448
449
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
450
451
    EventResult handleExMode(const Input &);
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
452
    EventResult handleCommandSubSubMode(const Input &);
453
454
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
455
    void resetCommandMode();
hjk's avatar
hjk committed
456
    void search(const QString &needle, bool forward, bool incSearch = false);
hjk's avatar
hjk committed
457
    void highlightMatches(const QString &needle);
458
    void stopIncrementalFind();
hjk's avatar
hjk committed
459

hjk's avatar
hjk committed
460
461
462
    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
463
464
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
465
466
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
467

hjk's avatar
hjk committed
468
    int lastPositionInDocument() const; // last valid pos in doc
469
470
    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
471
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
472
473
    QString lineContents(int line) const; // 1 based line
    void setLineContents(int line, const QString &contents) const; // 1 based line
hjk's avatar
hjk committed
474

hjk's avatar
hjk committed
475
476
    int linesOnScreen() const;
    int columnsOnScreen() const;
477
478
479
480
    int linesInDocument() const;

    // all zero-based counting
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
481
    int cursorLineInDocument() const;
482
483
484
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
485
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
486
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
487
488
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
489

490
491
492
493
494
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

495
    // helper functions for indenting
496
    bool isElectricCharacter(QChar c) const;
497
498
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
499
500
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
501

hjk's avatar
hjk committed
502
    void moveToFirstNonBlankOnLine();
503
    void moveToTargetColumn();
504
505
    void setTargetColumn() {
        m_targetColumn = leftDist();
506
        m_visualTargetColumn = m_targetColumn;
507
508
        //qDebug() << "TARGET: " << m_targetColumn;
    }
509
    void moveToNextWord(bool simple, bool deleteWord = false);
510
    void moveToMatchingParanthesis();
511
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
512
513

    // to reduce line noise
hjk's avatar
hjk committed
514
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
515
    void moveToStartOfLine();
hjk's avatar
hjk committed
516
    void moveToEndOfLine();
hjk's avatar
hjk committed
517
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
518
519
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
520
521
    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
522
    void setAnchor();
523
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
524
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
525

526
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
527

hjk's avatar
hjk committed
528
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
529
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
530
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
531

532
    void enterInsertMode();
533
    void enterReplaceMode();
534
    void enterCommandMode();
535
    void enterExMode();
hjk's avatar
hjk committed
536
537
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
538
    void notImplementedYet();
539
540
    void updateMiniBuffer();
    void updateSelection();
541
    void updateCursor();
542
    QWidget *editor() const;
543
544
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
545
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
546
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
547
548
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
549

550
551
552
    // 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);
553
554
555
    // 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);
556

557
558
559
560
561
    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
562
    void updateEditor();
563

564
565
566
567
568
569
570
    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);

571
572
573
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
574
    bool m_wasReadOnly; // saves read-only state of document
575

hjk's avatar
hjk committed
576
577
    FakeVimHandler *q;
    Mode m_mode;
578
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
579
    SubMode m_submode;
hjk's avatar
hjk committed
580
    SubSubMode m_subsubmode;
581
    Input m_subsubdata;
hjk's avatar
hjk committed
582
    QTextCursor m_tc;
583
    int m_oldPosition; // copy from last event to check for external changes
584
    int m_anchor;
hjk's avatar
hjk committed
585
    int m_register;
hjk's avatar
hjk committed
586
587
    QString m_mvcount;
    QString m_opcount;
588
589
    MoveType m_movetype;
    RangeMode m_rangemode;
590
    int m_visualInsertCount;
hjk's avatar
hjk committed
591
592

    bool m_fakeEnd;
hjk's avatar
hjk committed
593
594
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
595

hjk's avatar
hjk committed
596
    int m_gflag;  // whether current command started with 'g'
597

598
    QString m_commandPrefix;
hjk's avatar
hjk committed
599
    QString m_commandBuffer;
600
601
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
602

603
    bool m_lastSearchForward;
604
    bool m_findPending;
hjk's avatar
hjk committed
605
    QString m_lastInsertion;
606
    QString m_lastDeletion;
hjk's avatar
hjk committed
607

hjk's avatar
hjk committed
608
609
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
610

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

hjk's avatar
hjk committed
614
    void removeSelectedText();
615
    void removeText(const Range &range);
616
617
618
619
620
621
622
623
624
625
    void removeTransform(int, QTextCursor *);

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

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

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

627
628
    QString m_replacement;
    void replaceSelectedText(); // replace each character with m_replacement
629
630
    void replaceTransform(int, QTextCursor *);

631
632
633
634
635
636
637
    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);
638

639
    // undo handling
640
641
    void undo();
    void redo();
642
    void setUndoPosition(int pos);
643
    QMap<int, int> m_undoCursorPosition; // revision -> position
644
    bool m_beginEditBlock;
645

hjk's avatar
hjk committed
646
    // extra data for '.'
hjk's avatar
hjk committed
647
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
648
649
    void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
    void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
hjk's avatar
hjk committed
650

hjk's avatar
hjk committed
651
652
    // extra data for ';'
    QString m_semicolonCount;
653
654
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
655

hjk's avatar
hjk committed
656
    // history for '/'
hjk's avatar
hjk committed
657
    QString lastSearchString() const;
hjk's avatar
hjk committed
658

hjk's avatar
hjk committed
659
    // visual line mode
660
661
662
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
663

664
665
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
666
    QString m_oldNeedle;
667

hjk's avatar
hjk committed
668
    // vi style configuration
hjk's avatar
hjk committed
669
670
671
672
    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); }
673

674
675
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
676

677
    int m_cursorWidth;
678

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

688
    void recordJump();
689
    void recordNewUndo();
690
691
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
692
693

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

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

hjk's avatar
hjk committed
711
    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
712
713
714
715

    void setupCharClass();
    int charClass(QChar c, bool simple) const;
    signed char m_charClass[256];
hjk's avatar
hjk committed
716
717
718

    static struct GlobalData
    {
719
720
721
722
723
724
725
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
            searchHistoryIndex = 0;
            commandHistoryIndex = 0;
        }
hjk's avatar
hjk committed
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746

        // Input.
        QVector<Input> pendingInput;
        int inputTimer;

        // Repetition.
        QString dotCommand;
        bool inReplay; // true if we are executing a '.'

        // Histories.
        QStringList searchHistory;
        int searchHistoryIndex;
        QStringList commandHistory;
        int commandHistoryIndex;

        QHash<int, Register> registers;

        // All mappings.
        typedef QHash<char, ModeMapping> Mappings;
        Mappings mappings;
    } g;
hjk's avatar
hjk committed
747
748
};

hjk's avatar
hjk committed
749
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
750

751
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
752
{
hjk's avatar
hjk committed
753
    //static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
754
    q = parent;
755
756
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
757
    //new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
758
759
    init();
}
760

hjk's avatar
hjk committed
761
762
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
763
    m_mode = CommandMode;
hjk's avatar
hjk committed
764
765
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
766
    m_passing = false;
767
    m_findPending = false;
hjk's avatar
hjk committed
768
    m_fakeEnd = false;
769
    m_positionPastEnd = m_anchorPastEnd = false;
770
    m_lastSearchForward = true;
hjk's avatar
hjk committed
771
    m_register = '"';
hjk's avatar
hjk committed
772
    m_gflag = false;
773
    m_visualMode = NoVisualMode;
774
    m_targetColumn = 0;
775
    m_visualTargetColumn = 0;
776
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
777
    m_anchor = 0;
778
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
779
    m_justAutoIndented = 0;
780
    m_rangemode = RangeCharMode;
781
    m_beginEditBlock = true;
hjk's avatar
hjk committed
782
783

    setupCharClass();
hjk's avatar
hjk committed
784
785
}

786
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
787
{
788
789
790
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
791

792
    if (key == Key_Escape) {
793
        if (m_subsubmode == SearchSubSubMode)
794
            return true;
hjk's avatar
hjk committed
795
        // Not sure this feels good. People often hit Esc several times
796
797
798
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
799
800
801
    }

    // We are interested in overriding  most Ctrl key combinations
802
803
804
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
805
        // Ctrl-K is special as it is the Core's default notion of Locator
806
807
808
809
810
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
811
        }
812
813
814
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
815
816
    }

817
818
819
820
821
822
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
823
    const int key = ev->key();
824
825
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
826
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
827
828
829
830
831
832
833
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
834
        passShortcuts(false);
835
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
836
        //if (input.is(',')) { // use ',,' to leave, too.
837
838
839
840
841
842
843
844
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
845
846

    // Fake "End of line"
847
848
    m_tc = EDITOR(textCursor());

849
850
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
851
        setTargetColumn();
852
853
854
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
855
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
856
857
858
                Range range(m_oldPosition, m_tc.position());
                m_lastInsertion.append(text(range));
            }
859
860
861
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
862
863
        }
    }
864
865

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

hjk's avatar
hjk committed
867
    if (m_fakeEnd)
hjk's avatar
hjk committed
868
        moveRight();
hjk's avatar
hjk committed
869

870
871
872
873
874
875
876
    //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);
    //}
877

878
879
    QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
            || !m_tc.atBlockEnd() || m_tc.block().length() <= 1,
hjk's avatar
hjk committed
880
        qDebug() << "Cursor at EOL before key handler");
881

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

884
885
886
887
    // 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
888

889
890
        QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
                || !m_tc.atBlockEnd() || m_tc.block().length() <= 1,
hjk's avatar
hjk committed
891
            qDebug() << "Cursor at EOL after key handler");
892

893
894
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
895

896
897
898
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
899
    return result;
hjk's avatar
hjk committed
900
901
}

902
903
904
905
906
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

907
908
909
910
911
912
913
914
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
915
    m_wasReadOnly = EDITOR(isReadOnly());
916

hjk's avatar
hjk committed
917
918
    updateEditor();

919
920
921
922
923
924
925
926
927
928
929
930
931
932
    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();
    }

933
    updateMiniBuffer();
934
    updateCursor();
935
936
}

hjk's avatar
hjk committed
937
938
939
940
void FakeVimHandler::Private::updateEditor()
{
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
hjk's avatar
hjk committed
941
942

    setupCharClass();
hjk's avatar
hjk committed
943
944
}