fakevimhandler.cpp 154 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>
hjk's avatar
hjk committed
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,
hjk's avatar
hjk committed
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
164
    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
    ReplaceSubMode,    // Used for r
hjk's avatar
hjk committed
165
};
hjk's avatar
hjk committed
166

hjk's avatar
hjk committed
167
168
169
/*! 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
170
171
172
enum SubSubMode
{
    NoSubSubMode,
hjk's avatar
hjk committed
173
174
175
176
177
178
179
180
181
    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.
    TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
    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
222
223
224
225
226
227

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

228
struct Column
229
{
230
    Column(int p, int l) : physical(p), logical(l) {}
hjk's avatar
hjk committed
231
232
    int physical; // Number of characters in the data.
    int logical; // Column on screen.
233
234
};

235
236
237
238
239
QDebug operator<<(QDebug ts, const Column &col)
{
    return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
}

240
241
242
243
244
245
246
247
248
249
250
251
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
252
    Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
253
254
255
256
257
    Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
    QString contents;
    RangeMode rangemode;
};

hjk's avatar
hjk committed
258
QDebug operator<<(QDebug ts, const Register &reg)
259
{
hjk's avatar
hjk committed
260
261
    return ts << reg.contents;
}
262

hjk's avatar
hjk committed
263
264
265
struct SearchData
{
    SearchData() { init(); }
266

hjk's avatar
hjk committed
267
268
    void init() { forward = true; mustMove = true; highlightMatches = true;
        highlightCursor = true; }
269

hjk's avatar
hjk committed
270
271
272
273
274
    QString needle;
    bool forward;
    bool mustMove;
    bool highlightMatches;
    bool highlightCursor;
275
276
};

hjk's avatar
hjk committed
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

Range::Range()
    : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}

Range::Range(int b, int e, RangeMode m)
    : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
{}

QString Range::toString() const
{
    return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
        .arg(rangemode);
}

QDebug operator<<(QDebug ts, const Range &range)
{
    return ts << '[' << range.beginPos << ',' << range.endPos << ']';
}



ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
    : cmd(c), hasBang(false), args(a), range(r)
{}

QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
    return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
}

QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
309
{
310
    foreach (const QTextEdit::ExtraSelection &sel, sels)
311
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
312
313
    return ts;
}
314

hjk's avatar
hjk committed
315
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
316
{
hjk's avatar
hjk committed
317
318
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
hjk's avatar
hjk committed
319
320
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
321
322
        if (c.isPrint())
            res += c;
hjk's avatar
hjk committed
323
324
        else if (cc == '\n')
            res += _("<CR>");
hjk's avatar
hjk committed
325
        else
hjk's avatar
hjk committed
326
            res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
327
328
    }
    return res;
hjk's avatar
hjk committed
329
330
}

331
332
333
334
335
336
337
338
339
340
341
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;
}

hjk's avatar
hjk committed
342
inline QString msgMarkNotSet(const QString &text)
343
{
hjk's avatar
hjk committed
344
    return FakeVimHandler::tr("Mark '%1' not set").arg(text);
345
346
}

hjk's avatar
hjk committed
347
class Input
hjk's avatar
hjk committed
348
{
hjk's avatar
hjk committed
349
public:
350
351
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
352

353
354
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
355

hjk's avatar
hjk committed
356
    Input(int k, int m, const QString &t)
357
358
        : m_key(k), m_modifiers(m), m_text(t)
    {
hjk's avatar
hjk committed
359
360
361
362
363
364
        // On Mac, QKeyEvent::text() returns non-empty strings for
        // cursor keys. This breaks some of the logic later on
        // relying on text() being empty for "special" keys.
        // FIXME: Check the real conditions.
        if (m_text.size() == 1 && m_text.at(0).unicode() < ' ')
            m_text.clear();
365
366
367
368
369
370
371
372
        // 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';
    }
hjk's avatar
hjk committed
373

374
375
376
377
378
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

hjk's avatar
hjk committed
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

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

    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

395
396
    bool is(int c) const
    {
397
398
399
        return m_xkey == c && (m_modifiers == 0
                || m_modifiers == Qt::ShiftModifier
                || m_modifiers == Qt::GroupSwitchModifier);
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
    }

    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
415
416
        return a.m_key == m_key && a.m_modifiers == m_modifiers
            && m_text == a.m_text;
417
418
    }

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

421
422
    QString text() const { return m_text; }

hjk's avatar
hjk committed
423
424
425
426
427
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

428
429
    int key() const { return m_key; }

430
431
432
433
434
435
436
437
438
    QChar raw() const
    {
        if (m_key == Key_Tab)
            return '\t';
        if (m_key == Key_Return)
            return '\n';
        return m_key;
    }

hjk's avatar
hjk committed
439
440
441
442
443
    QDebug dump(QDebug ts) const
    {
        return ts << m_key << '-' << m_modifiers << '-'
            << quoteUnprintable(m_text);
    }
444
445
446
447
448
449
private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
450

hjk's avatar
hjk committed
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
{
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};


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

class History
{
public:
    History() : m_index(0) {}
    void append(const QString &item)
        { //qDebug() << "APP: " << item << m_items;
            m_items.removeAll(item);
            m_items.append(item); m_index = m_items.size() - 1;  }
    void down() { m_index = qMin(m_index + 1, m_items.size()); }
    void up() { m_index = qMax(m_index - 1, 0); }
    //void clear() { m_items.clear(); m_index = 0; }
    void restart() { m_index = m_items.size(); }
    QString current() const { return m_items.value(m_index, QString()); }
    QStringList items() const { return m_items; }
private:
    QStringList m_items;
    int m_index;
};

hjk's avatar
hjk committed
510
511

// Mappings for a specific mode.
hjk's avatar
hjk committed
512
class ModeMapping : public QList<QPair<Inputs, Inputs> >
hjk's avatar
hjk committed
513
514
515
516
517
518
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
519
520
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
    }

    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
542
543
    // 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
544
    // and replace *input with the mapped data.
hjk's avatar
hjk committed
545
    bool mappingDone(Inputs *inputs) const
hjk's avatar
hjk committed
546
547
548
    {
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
hjk's avatar
hjk committed
549
            const Inputs &haystack = at(i).first;
550
            // A mapping
hjk's avatar
hjk committed
551
552
            if (startsWith(haystack, *inputs)) {
                if (haystack.size() != inputs->size())
hjk's avatar
hjk committed
553
554
                    return false; // This can be extended.
                // Actual mapping.
hjk's avatar
hjk committed
555
                *inputs = at(i).second;
hjk's avatar
hjk committed
556
557
558
                return true;
            }
        }
hjk's avatar
hjk committed
559
        // No extensible mapping found. Use inputs as-is.
hjk's avatar
hjk committed
560
561
562
563
564
565
566
567
568
569
        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
570
            if (needle.at(i) != haystack.at(i))
hjk's avatar
hjk committed
571
572
573
574
575
576
                return false;
        }
        return true;
    }
};

hjk's avatar
hjk committed
577

hjk's avatar
hjk committed
578
579
580
581
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
582
public:
583
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
584

585
586
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
587
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
588
    void handleExCommand(const QString &cmd);
589

hjk's avatar
hjk committed
590
    // Updates marks positions by the difference in positionChange.
591
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
592

593
    void installEventFilter();
594
    void passShortcuts(bool enable);
595
    void setupWidget();
596
    void restoreWidget(int tabSize);
597

598
    friend class FakeVimHandler;
hjk's avatar
hjk committed
599
600

    void init();
hjk's avatar
hjk committed
601
602
603
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
hjk's avatar
hjk committed
604
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
605
606
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
607
608
    EventResult handleExMode(const Input &);
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
609
    EventResult handleCommandSubSubMode(const Input &);
610
611
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
612
    void resetCommandMode();
hjk's avatar
hjk committed
613
    void search(const SearchData &sd);
hjk's avatar
hjk committed
614
    void highlightMatches(const QString &needle);
615
    void stopIncrementalFind();
hjk's avatar
hjk committed
616

hjk's avatar
hjk committed
617
618
619
    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
620
621
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
622
623
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
624

hjk's avatar
hjk committed
625
    int lastPositionInDocument() const; // Returns last valid position in doc.
626
627
    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
628
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
629
    QString lineContents(int line) const; // 1 based line
hjk's avatar
hjk committed
630
    void setLineContents(int line, const QString &contents); // 1 based line
hjk's avatar
hjk committed
631

hjk's avatar
hjk committed
632
633
    int linesOnScreen() const;
    int columnsOnScreen() const;
634
635
    int linesInDocument() const;

hjk's avatar
hjk committed
636
    // The following use all zero-based counting.
637
    int cursorLineOnScreen() const;
638
639
640
641
642
643
644
645
    int cursorLine() const;
    int physicalCursorColumn() const; // as stored in the data
    int logicalCursorColumn() const; // as visible on screen
    int physicalToLogicalColumn(int physical, const QString &text) const;
    int logicalToPhysicalColumn(int logical, const QString &text) const;
    Column cursorColumn() const; // as visible on screen
    int firstVisibleLine() const;
    void scrollToLine(int line);
hjk's avatar
hjk committed
646
647
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
648

649
    CursorPosition cursorPosition() const
650
        { return CursorPosition(position(), firstVisibleLine()); }
651
    void setCursorPosition(const CursorPosition &p)
652
        { setPosition(p.position); scrollToLine(p.scrollLine); }
653

hjk's avatar
hjk committed
654
    // Helper functions for indenting/
655
    bool isElectricCharacter(QChar c) const;
656
657
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
658
659
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
660

hjk's avatar
hjk committed
661
    void moveToFirstNonBlankOnLine();
662
    void moveToTargetColumn();
663
    void setTargetColumn() {
664
        m_targetColumn = logicalCursorColumn();
665
        m_visualTargetColumn = m_targetColumn;
666
667
        //qDebug() << "TARGET: " << m_targetColumn;
    }
668
    void moveToNextWord(bool simple, bool deleteWord = false);
669
    void moveToMatchingParanthesis();
670
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
671

hjk's avatar
hjk committed
672
    // Convenience wrappers to reduce line noise.
hjk's avatar
hjk committed
673
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
674
    void moveToStartOfLine();
hjk's avatar
hjk committed
675
    void moveToEndOfLine();
hjk's avatar
hjk committed
676
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
677
678
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
679
680
    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
681
    void setAnchor();
682
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
683
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
684

685
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
686

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

690
    void enterInsertMode();
hjk's avatar
hjk committed
691
    void enterReplaceMode();
692
    void enterCommandMode();
693
    void enterExMode();
hjk's avatar
hjk committed
694
695
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
696
    void notImplementedYet();
697
698
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
699
    void updateCursor();
700
    QWidget *editor() const;
701
702
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
703
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
704
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
705
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
hjk's avatar
hjk committed
706
707
708
709
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN"); m_tc.joinPreviousEditBlock(); }
    void breakEditBlock()
        { m_tc.beginEditBlock(); m_tc.insertText("x");
          m_tc.deletePreviousChar(); m_tc.endEditBlock(); }
710

711
712
713
714
715
    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
716
    void updateEditor();
717

718
719
720
721
722
723
724
    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);

hjk's avatar
hjk committed
725
    Q_SLOT void importSelection();
726
    void insertInInsertMode(const QString &text);
hjk's avatar
hjk committed
727

728
729
730
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
731
    bool m_wasReadOnly; // saves read-only state of document
732

hjk's avatar
hjk committed
733
734
    FakeVimHandler *q;
    Mode m_mode;
735
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
736
    SubMode m_submode;
hjk's avatar
hjk committed
737
    SubSubMode m_subsubmode;
738
    Input m_subsubdata;
hjk's avatar
hjk committed
739
    QTextCursor m_tc;
740
    int m_oldPosition; // copy from last event to check for external changes
741
    int m_anchor;
hjk's avatar
hjk committed
742
    int m_register;
hjk's avatar
hjk committed
743
744
    QString m_mvcount;
    QString m_opcount;
745
746
    MoveType m_movetype;
    RangeMode m_rangemode;
747
    int m_visualInsertCount;
hjk's avatar
hjk committed
748
749

    bool m_fakeEnd;
hjk's avatar
hjk committed
750
751
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
752

hjk's avatar
hjk committed
753
    int m_gflag;  // whether current command started with 'g'
754

hjk's avatar
hjk committed
755
    QString m_commandPrefix;
hjk's avatar
hjk committed
756
    QString m_commandBuffer;
757
758
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
759

760
    bool m_lastSearchForward;
761
    bool m_findPending;
hjk's avatar
hjk committed
762
    QString m_lastInsertion;
hjk's avatar
hjk committed
763
    QString m_lastDeletion;
hjk's avatar
hjk committed
764

hjk's avatar
hjk committed
765
766
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
767

hjk's avatar
hjk committed
768
769
770
771
772
773
774
775
776
777
778
779
780
    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());

    void insertText(const Register &reg);
781
    void removeText(const Range &range);
hjk's avatar
hjk committed
782
    void removeTransform(TransformationData *td);
783

hjk's avatar
hjk committed
784
785
    void invertCase(const Range &range);
    void invertCaseTransform(TransformationData *td);
786

hjk's avatar
hjk committed
787
788
    void upCase(const Range &range);
    void upCaseTransform(TransformationData *td);
789

hjk's avatar
hjk committed
790
791
    void downCase(const Range &range);
    void downCaseTransform(TransformationData *td);
792

hjk's avatar
hjk committed
793
794
795
    void replaceText(const Range &range, const QString &str);
    void replaceByStringTransform(TransformationData *td);
    void replaceByCharTransform(TransformationData *td);
796

hjk's avatar
hjk committed
797
798
799
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
800
801
802
803

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

    void pasteText(bool afterCursor);
804

805
    // undo handling
806
807
    void undo();
    void redo();
808
    void setUndoPosition(int pos);
809
    QMap<int, int> m_undoCursorPosition; // revision -> position
810

hjk's avatar
hjk committed
811
    // extra data for '.'
hjk's avatar
hjk committed
812
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
813
814
    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
815

hjk's avatar
hjk committed
816
817
    // extra data for ';'
    QString m_semicolonCount;
818
819
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
820

hjk's avatar
hjk committed
821
    // visual line mode
822
823
824
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
825

826
    // marks as lines
hjk's avatar
hjk committed
827
828
    int mark(int code) const;
    void setMark(int code, int position);
829
830
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
831
    // vi style configuration
hjk's avatar
hjk committed
832
833
834
835
    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); }
836

837
838
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
839
    int m_cursorWidth;
840

hjk's avatar
hjk committed
841
    // auto-indent
842
    QString tabExpand(int len) const;
843
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
844
845
846
847
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
848
    void handleStartOfLine();
hjk's avatar
hjk committed
849

850
    void recordJump();
851
852
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
853
854

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
855
856
    QTextCursor m_searchCursor;
    QString m_oldNeedle;
hjk's avatar
hjk committed
857

hjk's avatar
hjk committed
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
    bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
    bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
    bool handleExBangCommand(const ExCommand &cmd);
    bool handleExDeleteCommand(const ExCommand &cmd);
    bool handleExGotoCommand(const ExCommand &cmd);
    bool handleExHistoryCommand(const ExCommand &cmd);
    bool handleExRegisterCommand(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 handleExShiftCommand(const ExCommand &cmd);
    bool handleExSourceCommand(const ExCommand &cmd);
    bool handleExSubstituteCommand(const ExCommand &cmd);
    bool handleExWriteCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
874
875

    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
876
877
878
879

    void setupCharClass();
    int charClass(QChar c, bool simple) const;
    signed char m_charClass[256];
880
    bool m_ctrlVActive;
hjk's avatar
hjk committed
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909

    static struct GlobalData
    {
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
        }

        // Input.
        Inputs pendingInput;
        int inputTimer;

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

        // History for searches.
        History searchHistory;

        // History for :ex commands.
        History commandHistory;

        QHash<int, Register> registers;

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

hjk's avatar
hjk committed
912
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
913

914
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
915
{
hjk's avatar
hjk committed
916
    //static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
917
    q = parent;
918
919
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
920
    //new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
921
922
    init();
}
923

hjk's avatar
hjk committed
924
925
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
926
    m_mode = CommandMode;
hjk's avatar
hjk committed
927
928
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
929
    m_passing = false;
930
    m_findPending = false;
hjk's avatar
hjk committed
931
    m_fakeEnd = false;
932
    m_positionPastEnd = m_anchorPastEnd = false;
933
    m_lastSearchForward = true;
hjk's avatar
hjk committed
934
    m_register = '"';
hjk's avatar
hjk committed
935
    m_gflag = false;
936
    m_visualMode = NoVisualMode;
937
    m_targetColumn = 0;
938
    m_visualTargetColumn = 0;
939
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
940
    m_anchor = 0;
941
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
942
    m_justAutoIndented = 0;
943
    m_rangemode = RangeCharMode;
944
    m_ctrlVActive = false;
hjk's avatar
hjk committed
945
946

    setupCharClass();
hjk's avatar
hjk committed
947
948
}

949
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
950
{
951
952
953
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
954

955
    if (key == Key_Escape) {
hjk's avatar
hjk committed
956
957
        if (m_subsubmode == SearchSubSubMode)
            return true;
hjk's avatar
hjk committed
958
        // Not sure this feels good. People often hit Esc several times
959
960
        if (isNoVisualMode() && m_mode == CommandMode
               && m_opcount.isEmpty() && m_mvcount.isEmpty())
961
962
            return false;
        return true;
963
964
965
    }

    // We are interested in overriding  most Ctrl key combinations
966
967
968
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
969
        // Ctrl-K is special as it is the Core's default notion of Locator
970
971
972
973
974
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
975
        }
976
977
978
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
979
980
    }

981
982
983
984
985
986
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
987
    const int key = ev->key();
988
989
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
990
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
991
992
993
994
995
996
997
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
998
        passShortcuts(false);
999
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
1000
        //if (input.is(',')) { // use ',,' to leave, too.
1001
1002
1003
1004
1005
1006
1007
1008
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
1009
1010

    // Fake "End of line"
1011
1012
    m_tc = EDITOR(textCursor());

1013
1014
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
1015
        setTargetColumn();
1016
1017
1018
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
1019
            if (dist > 0 && dist <= physicalCursorColumn()) {
1020
                Range range(m_oldPosition, m_tc.position());
hjk's avatar
hjk committed
1021
                m_lastInsertion.append(selectText(range));
1022
            }
1023
1024
1025
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
1026
1027