fakevimhandler.cpp 151 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.
hjk's avatar
hjk committed
59
//
60

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

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

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

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

88
#include <algorithm>
89
#include <climits>
90
#include <ctype.h>
91

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

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

106
using namespace Utils;
hjk's avatar
hjk committed
107
108
109
110

namespace FakeVim {
namespace Internal {

hjk's avatar
hjk committed
111
112
113
114
115
116
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////

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

128
129
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
130
const int ParagraphSeparator = 0x00002029;
hjk's avatar
hjk committed
131
typedef QLatin1String _;
hjk's avatar
hjk committed
132
133
134

using namespace Qt;

hjk's avatar
hjk committed
135
136
/*! A \e Mode represents one of the basic modes of operation of FakeVim.
*/
137

hjk's avatar
hjk committed
138
139
140
enum Mode
{
    InsertMode,
141
    ReplaceMode,
hjk's avatar
hjk committed
142
    CommandMode,
143
    ExMode,
hjk's avatar
hjk committed
144
};
hjk's avatar
hjk committed
145

hjk's avatar
hjk committed
146
147
148
/*! A \e SubMode is used for things that require one more data item
    and are 'nested' behind a \l Mode.
*/
hjk's avatar
hjk committed
149
150
151
enum SubMode
{
    NoSubMode,
hjk's avatar
hjk committed
152
153
154
155
156
157
158
159
160
161
162
163
    ChangeSubMode,     // Used for c
    DeleteSubMode,     // Used for d
    FilterSubMode,     // Used for !
    IndentSubMode,     // Used for =
    RegisterSubMode,   // Used for "
    ShiftLeftSubMode,  // Used for <
    ShiftRightSubMode, // Used for >
    TransformSubMode,  // Used for ~/gu/gU
    WindowSubMode,     // Used for Ctrl-w
    YankSubMode,       // Used for y
    ZSubMode,          // Used for z
    CapitalZSubMode    // Used for Z
hjk's avatar
hjk committed
164
};
hjk's avatar
hjk committed
165

hjk's avatar
hjk committed
166
167
168
/*! A \e SubSubMode is used for things that require one more data item
    and are 'nested' behind a \l SubMode.
*/
hjk's avatar
hjk committed
169
170
171
enum SubSubMode
{
    NoSubSubMode,
hjk's avatar
hjk committed
172
173
174
175
176
177
178
179
180
    FtSubSubMode,         // Used for f, F, t, T.
    MarkSubSubMode,       // Used for m.
    BackTickSubSubMode,   // Used for `.
    TickSubSubMode,       // Used for '.
    InvertCaseSubSubMode, // Used for ~.
    DownCaseSubSubMode,   // Used for gu.
    UpCaseSubSubMode,     // Used for gU.
    ReplaceSubSubMode,    // Used for r after visual mode.
    TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
181
    SearchSubSubMode,
hjk's avatar
hjk committed
182
183
};

184
185
186
187
188
189
190
191
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

hjk's avatar
hjk committed
192
193
194
195
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
hjk's avatar
hjk committed
196
    MoveLineWise,
hjk's avatar
hjk committed
197
198
};

hjk's avatar
hjk committed
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*!
    \enum RangeMode

    The \e RangeMode serves as a means to define how the "Range" between
    the \l cursor and the \l anchor position is to be interpreted.

    \value RangeCharMode   Entered by pressing \key v. The range includes
                           all characters between cursor and anchor.
    \value RangeLineMode   Entered by pressing \key V. The range includes
                           all lines between the line of the cursor and
                           the line of the anchor.
    \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
                           newline when deleting.
    \value RangeBlockMode  Entered by pressing \key Ctrl-v. The range includes
                           all characters with line and column coordinates
                           between line and columns coordinates of cursor and
                           anchor.
    \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
                           all characters in the affected lines up to the end
                           of these lines.
*/
220
221
enum RangeMode
{
hjk's avatar
hjk committed
222
223
224
225
    RangeCharMode,         // v
    RangeLineMode,         // V
    RangeLineModeExclusive,
    RangeBlockMode,        // Ctrl-v
226
    RangeBlockAndTailMode, // Ctrl-v for D and X
227
228
229
230
231
232
233
234
235
};

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

236
struct Column
237
{
238
    Column(int p, int l) : physical(p), logical(l) {}
hjk's avatar
hjk committed
239
240
    int physical; // Number of characters in the data.
    int logical; // Column on screen.
241
242
};

243
244
245
246
247
248
249
250
251
252
253
254
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) {}
hjk's avatar
hjk committed
255
    Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
256
257
258
259
260
261
262
263
264
265
266
267
268
    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
269
    {}
270

271
    QString toString() const
272
273
274
275
276
    {
        return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
            .arg(rangemode);
    }

277
278
279
280
281
    int beginPos;
    int endPos;
    RangeMode rangemode;
};

hjk's avatar
hjk committed
282
283
284
285
286
287
288
289
290
291
292
293
294
struct ExCommand
{
    ExCommand(const QString &c, int b = -1, int e = -1)
        : line(c), range(b, e, RangeLineMode) {}
    QString line;
    Range range;
};

QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
    return ts << cmd.line << cmd.range.beginPos << cmd.range.endPos;
}

295
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
296
{
297
    foreach (const QTextEdit::ExtraSelection &sel, sels)
298
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
299
300
    return ts;
}
301

hjk's avatar
hjk committed
302
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
303
{
hjk's avatar
hjk committed
304
305
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
306
307
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
308
309
        if (c.isPrint())
            res += c;
310
        else if (cc == '\n')
hjk's avatar
hjk committed
311
            res += _("<CR>");
hjk's avatar
hjk committed
312
        else
313
            res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
314
315
    }
    return res;
hjk's avatar
hjk committed
316
317
}

318
319
320
321
322
323
324
325
326
327
328
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;
}

329
inline QString msgMarkNotSet(const QString &text)
330
{
hjk's avatar
hjk committed
331
    return FakeVimHandler::tr("Mark '%1' not set").arg(text);
332
333
}

hjk's avatar
hjk committed
334
class Input
hjk's avatar
hjk committed
335
{
hjk's avatar
hjk committed
336
public:
337
338
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
339

340
341
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
342

hjk's avatar
hjk committed
343
    Input(int k, int m, const QString &t)
344
345
346
347
348
349
350
351
352
353
        : 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';
    }
354

355
356
357
358
359
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

360
361
362
363
364
365
366
367
368
369
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

    bool isReturn() const
    {
        return m_key == Key_Return;
    }

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
    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
    {
hjk's avatar
hjk committed
388
389
        return a.m_key == m_key && a.m_modifiers == m_modifiers
            && m_text == a.m_text;
390
391
    }

hjk's avatar
hjk committed
392
393
    bool operator!=(const Input &a) const { return !operator==(a); }

394
395
    QString text() const { return m_text; }

hjk's avatar
hjk committed
396
397
398
399
400
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

401
402
    int key() const { return m_key; }

hjk's avatar
hjk committed
403
404
405
406
407
    QDebug dump(QDebug ts) const
    {
        return ts << m_key << '-' << m_modifiers << '-'
            << quoteUnprintable(m_text);
    }
408
409
410
411
412
413
private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
414

hjk's avatar
hjk committed
415
416
417
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
418
{
hjk's avatar
hjk committed
419
420
421
422
423
424
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};

425

hjk's avatar
hjk committed
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
void Inputs::parseFrom(const QString &str)
{
    const int n = str.size();
    for (int i = 0; i < n; ++i) {
        uint c0 = str.at(i).unicode(), c1 = 0, c2 = 0, c3 = 0, c4 = 0, c5 = 0;
        if (i + 1 < n)
            c1 = str.at(i + 1).unicode();
        if (i + 2 < n)
            c2 = str.at(i + 2).unicode();
        if (i + 3 < n)
            c3 = str.at(i + 3).unicode();
        if (i + 4 < n)
            c4 = str.at(i + 4).unicode();
        if (i + 5 < n)
            c5 = str.at(i + 5).unicode();
        if (c0 == '<') {
            if ((c1 == 'C' || c1 == 'c') && c2 == '-' && c4 == '>') {
                uint c = (c3 < 90 ? c3 : c3 - 32);
                append(Input(c, Qt::ControlModifier, QString(QChar(c - 64))));
                i += 4;
            } else {
                append(Input(QLatin1Char(c0)));
            }
        } else {
            append(Input(QLatin1Char(c0)));
        }
    }
}
hjk's avatar
hjk committed
454
455

// Mappings for a specific mode.
hjk's avatar
hjk committed
456
class ModeMapping : public QList<QPair<Inputs, Inputs> >
hjk's avatar
hjk committed
457
458
459
460
461
462
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
463
464
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    }

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

hjk's avatar
hjk committed
486
487
    // Returns 'false' if more input is needed to decide whether a mapping
    // needs to be applied. If a decision can be made, return 'true',
hjk's avatar
hjk committed
488
    // and replace *input with the mapped data.
hjk's avatar
hjk committed
489
    bool mappingDone(Inputs *inputs) const
hjk's avatar
hjk committed
490
491
492
    {
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
hjk's avatar
hjk committed
493
            const Inputs &haystack = at(i).first;
494
            // A mapping
hjk's avatar
hjk committed
495
496
            if (startsWith(haystack, *inputs)) {
                if (haystack.size() != inputs->size())
hjk's avatar
hjk committed
497
498
                    return false; // This can be extended.
                // Actual mapping.
hjk's avatar
hjk committed
499
                *inputs = at(i).second;
hjk's avatar
hjk committed
500
501
502
                return true;
            }
        }
hjk's avatar
hjk committed
503
        // No extensible mapping found. Use inputs as-is.
hjk's avatar
hjk committed
504
505
506
507
508
509
510
511
512
513
        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) {
hjk's avatar
hjk committed
514
            if (needle.at(i) != haystack.at(i))
hjk's avatar
hjk committed
515
516
517
518
519
520
                return false;
        }
        return true;
    }
};

hjk's avatar
hjk committed
521

hjk's avatar
hjk committed
522
523
524
525
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
526
public:
527
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
528

529
530
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
531
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
532
    void handleExCommand(const QString &cmd);
533

hjk's avatar
hjk committed
534
    // Updates marks positions by the difference in positionChange.
535
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
536

537
    void installEventFilter();
538
    void passShortcuts(bool enable);
539
    void setupWidget();
540
    void restoreWidget(int tabSize);
541

542
    friend class FakeVimHandler;
hjk's avatar
hjk committed
543
544

    void init();
hjk's avatar
hjk committed
545
546
547
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
548
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
549
550
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
551
552
    EventResult handleExMode(const Input &);
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
553
    EventResult handleCommandSubSubMode(const Input &);
554
555
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
556
    void resetCommandMode();
hjk's avatar
hjk committed
557
    void search(const QString &needle, bool forward, bool incSearch = false);
hjk's avatar
hjk committed
558
    void highlightMatches(const QString &needle);
559
    void stopIncrementalFind();
hjk's avatar
hjk committed
560

hjk's avatar
hjk committed
561
562
563
    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
564
565
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
566
567
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
568

hjk's avatar
hjk committed
569
    int lastPositionInDocument() const; // Returns last valid position in doc.
570
571
    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
572
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
573
    QString lineContents(int line) const; // 1 based line
hjk's avatar
hjk committed
574
    void setLineContents(int line, const QString &contents); // 1 based line
hjk's avatar
hjk committed
575

hjk's avatar
hjk committed
576
577
    int linesOnScreen() const;
    int columnsOnScreen() const;
578
579
    int linesInDocument() const;

hjk's avatar
hjk committed
580
    // The following use all zero-based counting.
581
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
582
    int cursorLineInDocument() const;
583
584
585
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
586
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
587
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
588
589
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
590

591
592
593
594
595
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

hjk's avatar
hjk committed
596
    // Helper functions for indenting/
597
    bool isElectricCharacter(QChar c) const;
598
599
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
600
601
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
602

hjk's avatar
hjk committed
603
    void moveToFirstNonBlankOnLine();
604
    void moveToTargetColumn();
605
606
    void setTargetColumn() {
        m_targetColumn = leftDist();
607
        m_visualTargetColumn = m_targetColumn;
608
609
        //qDebug() << "TARGET: " << m_targetColumn;
    }
610
    void moveToNextWord(bool simple, bool deleteWord = false);
611
    void moveToMatchingParanthesis();
612
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
613

hjk's avatar
hjk committed
614
    // Convenience wrappers to reduce line noise.
hjk's avatar
hjk committed
615
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
616
    void moveToStartOfLine();
hjk's avatar
hjk committed
617
    void moveToEndOfLine();
hjk's avatar
hjk committed
618
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
619
620
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
621
622
    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
623
    void setAnchor();
624
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
625
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
626

627
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
628

hjk's avatar
hjk committed
629
    // Helper function for handleExCommand returning 1 based line index.
hjk's avatar
hjk committed
630
631
    int readLineCode(QString &cmd);

632
    void enterInsertMode();
633
    void enterReplaceMode();
634
    void enterCommandMode();
635
    void enterExMode();
hjk's avatar
hjk committed
636
637
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
638
    void notImplementedYet();
639
640
    void updateMiniBuffer();
    void updateSelection();
641
    void updateCursor();
642
    QWidget *editor() const;
643
644
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
645
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
646
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
647
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
648
649
650
651
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN"); m_tc.joinPreviousEditBlock(); }
    void breakEditBlock()
        { m_tc.beginEditBlock(); m_tc.insertText("x");
          m_tc.deletePreviousChar(); m_tc.endEditBlock(); }
652

653
654
655
    // 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);
656
657
658
    // 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);
659

660
661
662
663
664
    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
665
    void updateEditor();
666

667
668
669
670
671
672
673
    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);

674
675
    Q_SLOT void importSelection();

676
677
678
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
679
    bool m_wasReadOnly; // saves read-only state of document
680

hjk's avatar
hjk committed
681
682
    FakeVimHandler *q;
    Mode m_mode;
683
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
684
    SubMode m_submode;
hjk's avatar
hjk committed
685
    SubSubMode m_subsubmode;
686
    Input m_subsubdata;
hjk's avatar
hjk committed
687
    QTextCursor m_tc;
688
    int m_oldPosition; // copy from last event to check for external changes
689
    int m_anchor;
hjk's avatar
hjk committed
690
    int m_register;
hjk's avatar
hjk committed
691
692
    QString m_mvcount;
    QString m_opcount;
693
694
    MoveType m_movetype;
    RangeMode m_rangemode;
695
    int m_visualInsertCount;
hjk's avatar
hjk committed
696
697

    bool m_fakeEnd;
hjk's avatar
hjk committed
698
699
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
700

hjk's avatar
hjk committed
701
    int m_gflag;  // whether current command started with 'g'
702

703
    QString m_commandPrefix;
hjk's avatar
hjk committed
704
    QString m_commandBuffer;
705
706
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
707

708
    bool m_lastSearchForward;
709
    bool m_findPending;
hjk's avatar
hjk committed
710
    QString m_lastInsertion;
711
    QString m_lastDeletion;
hjk's avatar
hjk committed
712

hjk's avatar
hjk committed
713
714
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
715

716
717
718
719
720
721
722
723
724
725
726
    struct TransformationData
    {
        TransformationData(const QString &s, const QVariant &d)
            : from(s), extraData(d) {}
        QString from;
        QString to;
        QVariant extraData;
    };
    typedef void (Private::*Transformation)(TransformationData *td);
    void transformText(const Range &range, Transformation transformation,
        const QVariant &extraData = QVariant());
727

hjk's avatar
hjk committed
728
    void insertText(const Register &reg);
729
    void removeText(const Range &range);
730
    void removeTransform(TransformationData *td);
731

hjk's avatar
hjk committed
732
    void invertCase(const Range &range);
733
    void invertCaseTransform(TransformationData *td);
734

hjk's avatar
hjk committed
735
    void upCase(const Range &range);
736
    void upCaseTransform(TransformationData *td);
737

hjk's avatar
hjk committed
738
    void downCase(const Range &range);
739
    void downCaseTransform(TransformationData *td);
740

741
    QString m_replacement;
hjk's avatar
hjk committed
742
    void replaceText(const Range &range, const QString &str);
743
    void replaceTransform(TransformationData *td);
744

hjk's avatar
hjk committed
745
746
747
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
748
749
750
751

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

    void pasteText(bool afterCursor);
752

753
    // undo handling
754
755
    void undo();
    void redo();
756
    void setUndoPosition(int pos);
757
    QMap<int, int> m_undoCursorPosition; // revision -> position
758

hjk's avatar
hjk committed
759
    // extra data for '.'
hjk's avatar
hjk committed
760
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
761
762
    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
763

hjk's avatar
hjk committed
764
765
    // extra data for ';'
    QString m_semicolonCount;
766
767
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
768

hjk's avatar
hjk committed
769
    // history for '/'
hjk's avatar
hjk committed
770
    QString lastSearchString() const;
hjk's avatar
hjk committed
771

hjk's avatar
hjk committed
772
    // visual line mode
773
774
775
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
776

777
    // marks as lines
hjk's avatar
hjk committed
778
779
    int mark(int code) const;
    void setMark(int code, int position);
780
781
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
782
    // vi style configuration
hjk's avatar
hjk committed
783
784
785
786
    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); }
787

788
789
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
790
    int m_cursorWidth;
791

hjk's avatar
hjk committed
792
    // auto-indent
793
    QString tabExpand(int len) const;
794
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
795
796
797
798
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
799
    void handleStartOfLine();
hjk's avatar
hjk committed
800

801
    void recordJump();
802
803
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
804
805

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
806
    QString m_oldNeedle;
hjk's avatar
hjk committed
807

hjk's avatar
hjk committed
808
809
810
811
812
813
814
815
816
817
818
819
820
821
    bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
    bool handleExBangCommand(const ExCommand &cmd);
    bool handleExDeleteCommand(const ExCommand &cmd);
    bool handleExGotoCommand(const ExCommand &cmd);
    bool handleExHistoryCommand(const ExCommand &cmd);
    bool handleExMapCommand(const ExCommand &cmd);
    bool handleExNormalCommand(const ExCommand &cmd);
    bool handleExReadCommand(const ExCommand &cmd);
    bool handleExRedoCommand(const ExCommand &cmd);
    bool handleExSetCommand(const ExCommand &cmd);
    bool handleExShiftRightCommand(const ExCommand &cmd);
    bool handleExSourceCommand(const ExCommand &cmd);
    bool handleExSubstituteCommand(const ExCommand &cmd);
    bool handleExWriteCommand(const ExCommand &cmd);
822

hjk's avatar
hjk committed
823
    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
824
825
826
827

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

    static struct GlobalData
    {
831
832
833
834
835
836
837
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
            searchHistoryIndex = 0;
            commandHistoryIndex = 0;
        }
hjk's avatar
hjk committed
838
839

        // Input.
hjk's avatar
hjk committed
840
        Inputs pendingInput;
hjk's avatar
hjk committed
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
        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
859
860
};

hjk's avatar
hjk committed
861
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
862

863
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
864
{
hjk's avatar
hjk committed
865
    //static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
866
    q = parent;
867
868
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
869
    //new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
870
871
    init();
}
872

hjk's avatar
hjk committed
873
874
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
875
    m_mode = CommandMode;
hjk's avatar
hjk committed
876
877
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
878
    m_passing = false;
879
    m_findPending = false;
hjk's avatar
hjk committed
880
    m_fakeEnd = false;
881
    m_positionPastEnd = m_anchorPastEnd = false;
882
    m_lastSearchForward = true;
hjk's avatar
hjk committed
883
    m_register = '"';
hjk's avatar
hjk committed
884
    m_gflag = false;
885
    m_visualMode = NoVisualMode;
886
    m_targetColumn = 0;
887
    m_visualTargetColumn = 0;
888
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
889
    m_anchor = 0;
890
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
891
    m_justAutoIndented = 0;
892
    m_rangemode = RangeCharMode;
hjk's avatar
hjk committed
893
894

    setupCharClass();
hjk's avatar
hjk committed
895
896
}

897
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
898
{
899
900
901
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
902

903
    if (key == Key_Escape) {
904
        if (m_subsubmode == SearchSubSubMode)
905
            return true;
hjk's avatar
hjk committed
906
        // Not sure this feels good. People often hit Esc several times
907
908
909
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
910
911
912
    }

    // We are interested in overriding  most Ctrl key combinations
913
914
915
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
916
        // Ctrl-K is special as it is the Core's default notion of Locator
917
918
919
920
921
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
922
        }
923
924
925
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
926
927
    }

928
929
930
931
932
933
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
934
    const int key = ev->key();
935
936
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
937
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
938
939
940
941
942
943
944
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
945
        passShortcuts(false);
946
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
947
        //if (input.is(',')) { // use ',,' to leave, too.
948
949
950
951
952
953
954
955
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
956
957

    // Fake "End of line"
958
959
    m_tc = EDITOR(textCursor());

960
961
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
962
        setTargetColumn();
963
964
965
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
966
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
967
                Range range(m_oldPosition, m_tc.position());
hjk's avatar
hjk committed
968
                m_lastInsertion.append(selectText(range));
969
            }
970
971
972
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
973
974
        }
    }
975
976

    m_tc.setVisualNavigation(true);