fakevimhandler.cpp 157 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
164
165
166
    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
    OpenSquareSubMode,   // Used for [
    CloseSquareSubMode,  // Used for ]
hjk's avatar
hjk committed
167
};
hjk's avatar
hjk committed
168

hjk's avatar
hjk committed
169
170
171
/*! 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
172
173
174
enum SubSubMode
{
    NoSubSubMode,
hjk's avatar
hjk committed
175
176
177
178
179
180
181
182
    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.
183
    SearchSubSubMode,
hjk's avatar
hjk committed
184
185
};

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

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

hjk's avatar
hjk committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*!
    \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.
*/
222
223
224
225
226
227
228
229

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

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

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

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

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

hjk's avatar
hjk committed
265
266
267
268
269
270
271
272
273
274
275
276
277
struct SearchData
{
    SearchData() { init(); }

    void init() { forward = true; mustMove = true; highlightMatches = true;
        highlightCursor = true; }

    QString needle;
    bool forward;
    bool mustMove;
    bool highlightMatches;
    bool highlightCursor;
};
278
279


280
281
282
Range::Range()
    : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
283

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

288
QString Range::toString() const
hjk's avatar
hjk committed
289
{
290
291
292
293
294
295
296
297
298
299
300
301
302
303
    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)
{}
hjk's avatar
hjk committed
304
305
306

QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
307
    return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
hjk's avatar
hjk committed
308
309
}

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

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

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

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

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

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

hjk's avatar
hjk committed
358
    Input(int k, int m, const QString &t)
359
360
        : m_key(k), m_modifiers(m), m_text(t)
    {
hjk's avatar
hjk committed
361
362
363
364
365
366
        // 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();
367
368
369
370
371
372
373
374
        // 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';
    }
375

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

381
382
383
384
385
386
387
388
389
390
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

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

hjk's avatar
hjk committed
391
392
393
394
395
396
    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

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

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

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

423
424
    QString text() const { return m_text; }

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

430
431
    int key() const { return m_key; }

432
433
434
435
436
437
438
439
440
    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
441
442
443
444
445
    QDebug dump(QDebug ts) const
    {
        return ts << m_key << '-' << m_modifiers << '-'
            << quoteUnprintable(m_text);
    }
446
447
448
449
450
451
private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
452

hjk's avatar
hjk committed
453
454
455
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
456
{
hjk's avatar
hjk committed
457
458
459
460
461
462
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};

463

hjk's avatar
hjk committed
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
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
492

hjk's avatar
hjk committed
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
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
513
// Mappings for a specific mode.
hjk's avatar
hjk committed
514
class ModeMapping : public QList<QPair<Inputs, Inputs> >
hjk's avatar
hjk committed
515
516
517
518
519
520
{
public:
    ModeMapping() { test(); }

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

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

hjk's avatar
hjk committed
579

hjk's avatar
hjk committed
580
581
582
583
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

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

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

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

595
    void installEventFilter();
596
    void passShortcuts(bool enable);
597
    void setupWidget();
598
    void restoreWidget(int tabSize);
599

600
    friend class FakeVimHandler;
hjk's avatar
hjk committed
601
602

    void init();
hjk's avatar
hjk committed
603
604
605
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
606
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
607
608
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
609
    EventResult handleExMode(const Input &);
hjk's avatar
hjk committed
610
611
    EventResult handleOpenSquareSubMode(const Input &);
    EventResult handleCloseSquareSubMode(const Input &);
hjk's avatar
hjk committed
612
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
613
    EventResult handleCommandSubSubMode(const Input &);
614
615
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
616
    void resetCommandMode();
hjk's avatar
hjk committed
617
    void search(const SearchData &sd);
hjk's avatar
hjk committed
618
    void searchBalanced(bool forward, QChar needle, QChar other);
hjk's avatar
hjk committed
619
    void highlightMatches(const QString &needle);
620
    void stopIncrementalFind();
hjk's avatar
hjk committed
621

hjk's avatar
hjk committed
622
623
624
    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
625
626
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
627
628
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
629

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

hjk's avatar
hjk committed
637
638
    int linesOnScreen() const;
    int columnsOnScreen() const;
639
640
    int linesInDocument() const;

hjk's avatar
hjk committed
641
    // The following use all zero-based counting.
642
    int cursorLineOnScreen() const;
643
644
645
646
647
648
649
650
    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
651
652
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
653

654
    CursorPosition cursorPosition() const
655
        { return CursorPosition(position(), firstVisibleLine()); }
656
    void setCursorPosition(const CursorPosition &p)
657
        { setPosition(p.position); scrollToLine(p.scrollLine); }
658

hjk's avatar
hjk committed
659
    // Helper functions for indenting/
660
    bool isElectricCharacter(QChar c) const;
661
    void indentSelectedText(QChar lastTyped = QChar());
662
    void indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
663
664
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
665

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

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

690
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
691

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

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

716
717
718
719
720
    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
721
    void updateEditor();
722

723
724
725
726
727
728
729
    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);

730
    Q_SLOT void importSelection();
731
    void insertInInsertMode(const QString &text);
732

733
734
735
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
736
    bool m_wasReadOnly; // saves read-only state of document
737

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

    bool m_fakeEnd;
hjk's avatar
hjk committed
755
756
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
757

hjk's avatar
hjk committed
758
    int m_gflag;  // whether current command started with 'g'
759

760
    QString m_commandPrefix;
hjk's avatar
hjk committed
761
    QString m_commandBuffer;
762
763
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
764

765
    bool m_lastSearchForward;
766
    bool m_findPending;
hjk's avatar
hjk committed
767
    QString m_lastInsertion;
768
    QString m_lastDeletion;
hjk's avatar
hjk committed
769

hjk's avatar
hjk committed
770
771
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
772

773
774
775
776
777
778
779
780
781
782
783
    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());
784

hjk's avatar
hjk committed
785
    void insertText(const Register &reg);
786
    void removeText(const Range &range);
787
    void removeTransform(TransformationData *td);
788

hjk's avatar
hjk committed
789
    void invertCase(const Range &range);
790
    void invertCaseTransform(TransformationData *td);
791

hjk's avatar
hjk committed
792
    void upCase(const Range &range);
793
    void upCaseTransform(TransformationData *td);
794

hjk's avatar
hjk committed
795
    void downCase(const Range &range);
796
    void downCaseTransform(TransformationData *td);
797

hjk's avatar
hjk committed
798
    void replaceText(const Range &range, const QString &str);
799
800
    void replaceByStringTransform(TransformationData *td);
    void replaceByCharTransform(TransformationData *td);
801

hjk's avatar
hjk committed
802
803
804
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
805
806
807
808

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

    void pasteText(bool afterCursor);
809

810
    // undo handling
811
812
    void undo();
    void redo();
813
    void setUndoPosition(int pos);
814
    QMap<int, int> m_undoCursorPosition; // revision -> position
815

hjk's avatar
hjk committed
816
    // extra data for '.'
hjk's avatar
hjk committed
817
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
818
819
    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
820

hjk's avatar
hjk committed
821
822
    // extra data for ';'
    QString m_semicolonCount;
823
824
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
825

hjk's avatar
hjk committed
826
    // visual line mode
827
828
829
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
830

831
    // marks as lines
hjk's avatar
hjk committed
832
833
    int mark(int code) const;
    void setMark(int code, int position);
834
835
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
836
    // vi style configuration
hjk's avatar
hjk committed
837
838
839
840
    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); }
841

842
843
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
844
    int m_cursorWidth;
845

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

855
    void recordJump();
856
857
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
858
859

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
860
    QTextCursor m_searchCursor;
hjk's avatar
hjk committed
861
    QString m_oldNeedle;
hjk's avatar
hjk committed
862

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

hjk's avatar
hjk committed
880
    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
881
882
883
884

    void setupCharClass();
    int charClass(QChar c, bool simple) const;
    signed char m_charClass[256];
885
    bool m_ctrlVActive;
hjk's avatar
hjk committed
886
887
888

    static struct GlobalData
    {
889
890
891
892
893
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
        }
hjk's avatar
hjk committed
894
895

        // Input.
hjk's avatar
hjk committed
896
        Inputs pendingInput;
hjk's avatar
hjk committed
897
898
899
900
901
902
        int inputTimer;

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

hjk's avatar
hjk committed
903
904
905
906
907
        // History for searches.
        History searchHistory;

        // History for :ex commands.
        History commandHistory;
hjk's avatar
hjk committed
908
909
910
911
912
913
914

        QHash<int, Register> registers;

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

hjk's avatar
hjk committed
917
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
918

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

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

    setupCharClass();
hjk's avatar
hjk committed
952
953
}

954
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
955
{
956
957
958
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
959

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

    // We are interested in overriding  most Ctrl key combinations
971
972
973
    if (mods == Qt