fakevimhandler.cpp 164 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
**
hjk's avatar
hjk committed
7
** Contact: Nokia Corporation (info@qt.nokia.com)
hjk's avatar
hjk committed
8
9
**
**
10
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
11
**
hjk's avatar
hjk committed
12
13
14
15
16
17
** 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
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21
22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23
24
25
26
27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
** If you have questions regarding the use of this file, please contact
Tobias Hunger's avatar
Tobias Hunger committed
29
** Nokia at info@qt.nokia.com.
hjk's avatar
hjk committed
30
**
31
**************************************************************************/
hjk's avatar
hjk committed
32

33
//
34
35
36
37
38
39
40
41
42
43
44
// 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:
45
//
46
47
48
//   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.
49
//
50
51
52
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
53
54
55
56
//   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().
//
57
//   A current "region of interest"
hjk's avatar
hjk committed
58
59
//   spans between anchor(), (i.e. the character below anchor()), and
//   position(). The character below position() is not included
60
//   if the last movement command was exclusive (MoveExclusive).
hjk's avatar
hjk committed
61
//
62

63
64
#include "fakevimhandler.h"

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

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

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

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

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

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

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

namespace FakeVim {
namespace Internal {

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

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

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

hjk's avatar
hjk committed
131
132
133
134
135
136
137
enum {
#ifdef Q_WS_MAC
    RealControlModifier = Qt::MetaModifier
#else
    RealControlModifier = Qt::ControlModifier
#endif
};
138
139
140
// Enforce use of RealControlModifier by breaking the compilation.
#define MetaModifier     // Use RealControlModifier instead
#define ControlModifier  // Use RealControlModifier instead
hjk's avatar
hjk committed
141

hjk's avatar
hjk committed
142
const int ParagraphSeparator = 0x00002029;
hjk's avatar
hjk committed
143
typedef QLatin1String _;
hjk's avatar
hjk committed
144
145
146

using namespace Qt;

hjk's avatar
hjk committed
147
148
/*! A \e Mode represents one of the basic modes of operation of FakeVim.
*/
149

hjk's avatar
hjk committed
150
151
152
enum Mode
{
    InsertMode,
153
    ReplaceMode,
hjk's avatar
hjk committed
154
    CommandMode,
155
    ExMode
hjk's avatar
hjk committed
156
};
hjk's avatar
hjk committed
157

hjk's avatar
hjk committed
158
159
160
/*! 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
161
162
163
enum SubMode
{
    NoSubMode,
hjk's avatar
hjk committed
164
165
166
167
168
169
170
171
172
173
174
175
176
177
    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 [
178
    CloseSquareSubMode  // Used for ]
hjk's avatar
hjk committed
179
};
hjk's avatar
hjk committed
180

hjk's avatar
hjk committed
181
182
183
/*! 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
184
185
186
enum SubSubMode
{
    NoSubSubMode,
hjk's avatar
hjk committed
187
188
189
190
191
192
193
194
    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.
195
    SearchSubSubMode
hjk's avatar
hjk committed
196
197
};

198
199
200
201
202
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
203
    VisualBlockMode
204
205
};

hjk's avatar
hjk committed
206
207
208
209
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
210
    MoveLineWise
hjk's avatar
hjk committed
211
212
};

hjk's avatar
hjk committed
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*!
    \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.
*/
234
235
236
237
238
239
240
241

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

242
struct Column
243
{
244
    Column(int p, int l) : physical(p), logical(l) {}
hjk's avatar
hjk committed
245
246
    int physical; // Number of characters in the data.
    int logical; // Column on screen.
247
248
};

249
250
251
252
253
QDebug operator<<(QDebug ts, const Column &col)
{
    return ts << "(p: " << col.physical << ", l: " << col.logical << ")";
}

254
255
256
257
258
259
260
261
262
263
264
265
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
266
    Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
267
268
269
270
271
    Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
    QString contents;
    RangeMode rangemode;
};

hjk's avatar
hjk committed
272
273
274
275
276
QDebug operator<<(QDebug ts, const Register &reg)
{
    return ts << reg.contents;
}

hjk's avatar
hjk committed
277
278
struct SearchData
{
hjk's avatar
hjk committed
279
280
281
282
283
284
285
    SearchData()
    {
        forward = true;
        mustMove = true;
        highlightMatches = true;
        highlightCursor = true;
    }
hjk's avatar
hjk committed
286
287
288
289
290
291
292

    QString needle;
    bool forward;
    bool mustMove;
    bool highlightMatches;
    bool highlightCursor;
};
293
294


295
296
297
Range::Range()
    : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
298

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

303
QString Range::toString() const
hjk's avatar
hjk committed
304
{
305
306
307
308
309
310
311
312
313
314
315
    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)
316
    : cmd(c), hasBang(false), args(a), range(r), count(1)
317
{}
hjk's avatar
hjk committed
318

319
320
321
322
323
bool ExCommand::matches(const QString &min, const QString &full) const
{
    return cmd.startsWith(min) && full.startsWith(cmd);
}

hjk's avatar
hjk committed
324
325
QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
326
    return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
hjk's avatar
hjk committed
327
328
}

329
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
330
{
331
    foreach (const QTextEdit::ExtraSelection &sel, sels)
332
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
333
334
    return ts;
}
335

hjk's avatar
hjk committed
336
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
337
{
hjk's avatar
hjk committed
338
339
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
340
341
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
342
343
        if (c.isPrint())
            res += c;
344
        else if (cc == '\n')
hjk's avatar
hjk committed
345
            res += _("<CR>");
hjk's avatar
hjk committed
346
        else
347
            res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
348
349
    }
    return res;
hjk's avatar
hjk committed
350
351
}

352
353
354
355
356
357
358
359
360
361
362
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;
}

363
inline QString msgMarkNotSet(const QString &text)
364
{
hjk's avatar
hjk committed
365
    return FakeVimHandler::tr("Mark '%1' not set").arg(text);
366
367
}

hjk's avatar
hjk committed
368
class Input
hjk's avatar
hjk committed
369
{
hjk's avatar
hjk committed
370
public:
371
372
373
    // Remove some extra "information" on Mac.
    static int cleanModifier(int m)  { return m & ~Qt::KeypadModifier; }

374
375
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
376

377
378
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
379

hjk's avatar
hjk committed
380
    Input(int k, int m, const QString &t)
381
        : m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
382
    {
hjk's avatar
hjk committed
383
384
385
386
387
388
        // 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();
389
390
391
392
393
394
395
396
        // 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';
    }
397

398
399
400
401
402
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

403
404
405
406
407
408
409
410
411
412
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

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

hjk's avatar
hjk committed
413
414
415
416
417
418
    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

419
420
    bool is(int c) const
    {
hjk's avatar
hjk committed
421
        return m_xkey == c && m_modifiers != RealControlModifier;
422
423
424
425
    }

    bool isControl(int c) const
    {
hjk's avatar
hjk committed
426
        return m_modifiers == RealControlModifier
427
            && (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
428
429
430
431
432
433
434
435
436
    }

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

    bool operator==(const Input &a) const
    {
hjk's avatar
hjk committed
437
438
        return a.m_key == m_key && a.m_modifiers == m_modifiers
            && m_text == a.m_text;
439
440
    }

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

443
444
    QString text() const { return m_text; }

hjk's avatar
hjk committed
445
446
447
448
449
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

450
451
    int key() const { return m_key; }

452
453
454
455
456
457
458
459
460
    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
461
462
463
464
465
    QDebug dump(QDebug ts) const
    {
        return ts << m_key << '-' << m_modifiers << '-'
            << quoteUnprintable(m_text);
    }
466
467
468
469
470
471
private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
472

hjk's avatar
hjk committed
473
474
475
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
476
{
hjk's avatar
hjk committed
477
478
479
480
481
482
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};

483

hjk's avatar
hjk committed
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
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);
hjk's avatar
hjk committed
502
                append(Input(c, RealControlModifier, QString(QChar(c - 64))));
hjk's avatar
hjk committed
503
                i += 4;
504
505
506
            } else if (c1 == 'C' && c2 == 'R' && c3 == '>') {
                append(Input(Key_Return, Qt::NoModifier, QString(QChar(13))));
                i += 3;
hjk's avatar
hjk committed
507
508
509
            } else if (c1 == 'E' && c2 == 's' && c3 == 'c' && c4 == '>') {
                append(Input(Key_Escape, Qt::NoModifier, QString(QChar(27))));
                i += 4;
hjk's avatar
hjk committed
510
511
512
513
514
515
516
517
            } else {
                append(Input(QLatin1Char(c0)));
            }
        } else {
            append(Input(QLatin1Char(c0)));
        }
    }
}
hjk's avatar
hjk committed
518

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
// This wraps a string and a "cursor position".
class CommandBuffer
{
public:
    CommandBuffer() : m_pos(0) {}

    void clear() { m_buffer.clear(); m_pos = 0; }
    void setContents(const QString &s) { m_buffer = s; m_pos = s.size(); }
    QString contents() const { return m_buffer; }
    bool isEmpty() const { return m_buffer.isEmpty(); }
    int cursorPos() const { return m_pos; }

    void insertChar(QChar c) { m_buffer.insert(m_pos++, c); }
    void insertText(const QString &s) { m_buffer.insert(m_pos, s); m_pos += s.size(); }
    void deleteChar() { if (m_pos) m_buffer.remove(--m_pos, 1); }

    void moveLeft() { if (m_pos) --m_pos; }
    void moveRight() { if (m_pos < m_buffer.size()) ++m_pos; }
    void moveStart() { m_pos = 0; }
    void moveEnd() { m_pos = m_buffer.size(); }

    QString display() const
    {
        QString msg;
        for (int i = 0; i != m_buffer.size(); ++i) {
            const QChar c = m_buffer.at(i);
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
        }
        return msg;
    }

    bool handleInput(const Input &input)
    {
        if (input.isKey(Key_Left)) {
            moveLeft();
        } else if (input.isKey(Key_Right)) {
            moveRight();
        } else if (input.isKey(Key_Home)) {
            moveStart();
        } else if (input.isKey(Key_End)) {
            moveEnd();
        } else if (input.isKey(Key_Delete)) {
            if (m_pos < m_buffer.size())
                m_buffer.remove(m_pos, 1);
        } else if (!input.text().isEmpty()) {
            insertText(input.text());
        } else {
            return false;
        }
        return true;
    }

private:
    QString m_buffer;
    int m_pos;
};


hjk's avatar
hjk committed
582
583
584
585
class History
{
public:
    History() : m_index(0) {}
586
    void append(const QString &item);
hjk's avatar
hjk committed
587
588
589
590
591
    void down() { m_index = qMin(m_index + 1, m_items.size()); }
    void up() { m_index = qMax(m_index - 1, 0); }
    void restart() { m_index = m_items.size(); }
    QString current() const { return m_items.value(m_index, QString()); }
    QStringList items() const { return m_items; }
592

hjk's avatar
hjk committed
593
594
595
596
597
private:
    QStringList m_items;
    int m_index;
};

598
599
600
601
602
603
604
void History::append(const QString &item)
{
    if (item.isEmpty())
        return;
    m_items.removeAll(item);
    m_items.append(item); m_index = m_items.size() - 1;
}
hjk's avatar
hjk committed
605

hjk's avatar
hjk committed
606
// Mappings for a specific mode.
hjk's avatar
hjk committed
607
class ModeMapping : public QList<QPair<Inputs, Inputs> >
hjk's avatar
hjk committed
608
609
610
611
612
613
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
614
615
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
    }

    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
637
638
    // 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
639
    // and replace *input with the mapped data.
hjk's avatar
hjk committed
640
    bool mappingDone(Inputs *inputs) const
hjk's avatar
hjk committed
641
642
643
    {
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
hjk's avatar
hjk committed
644
            const Inputs &haystack = at(i).first;
645
            // A mapping
hjk's avatar
hjk committed
646
647
            if (startsWith(haystack, *inputs)) {
                if (haystack.size() != inputs->size())
hjk's avatar
hjk committed
648
649
                    return false; // This can be extended.
                // Actual mapping.
hjk's avatar
hjk committed
650
                *inputs = at(i).second;
hjk's avatar
hjk committed
651
652
653
                return true;
            }
        }
hjk's avatar
hjk committed
654
        // No extensible mapping found. Use inputs as-is.
hjk's avatar
hjk committed
655
656
657
658
659
660
661
662
663
664
        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
665
            if (needle.at(i) != haystack.at(i))
hjk's avatar
hjk committed
666
667
668
669
670
671
                return false;
        }
        return true;
    }
};

hjk's avatar
hjk committed
672

hjk's avatar
hjk committed
673
674
675
676
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
677
public:
678
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
679

680
681
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
682
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
683
    void handleExCommand(const QString &cmd);
684

685
    void installEventFilter();
686
    void passShortcuts(bool enable);
687
    void setupWidget();
688
    void restoreWidget(int tabSize);
689

690
    friend class FakeVimHandler;
hjk's avatar
hjk committed
691
692

    void init();
hjk's avatar
hjk committed
693
694
695
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
696
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
697
    EventResult handleCommandMode(const Input &);
hjk's avatar
hjk committed
698
    EventResult handleCommandMode1(const Input &);
hjk's avatar
hjk committed
699
    EventResult handleCommandMode2(const Input &);
hjk's avatar
hjk committed
700
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
701
    EventResult handleExMode(const Input &);
hjk's avatar
hjk committed
702
703
    EventResult handleOpenSquareSubMode(const Input &);
    EventResult handleCloseSquareSubMode(const Input &);
hjk's avatar
hjk committed
704
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
705
    EventResult handleCommandSubSubMode(const Input &);
706
707
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
708
    void resetCommandMode();
hjk's avatar
hjk committed
709
    void search(const SearchData &sd);
hjk's avatar
hjk committed
710
    void searchBalanced(bool forward, QChar needle, QChar other);
hjk's avatar
hjk committed
711
    void highlightMatches(const QString &needle);
712
    void stopIncrementalFind();
hjk's avatar
hjk committed
713

hjk's avatar
hjk committed
714
715
716
    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
717
718
719
    QTextBlock block() const { return cursor().block(); }
    int leftDist() const { return position() - block().position(); }
    int rightDist() const { return block().length() - leftDist() - 1; }
hjk's avatar
hjk committed
720
721
    bool atBlockEnd() const { return cursor().atBlockEnd(); }
    bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
hjk's avatar
hjk committed
722

hjk's avatar
hjk committed
723
    int lastPositionInDocument() const; // Returns last valid position in doc.
724
725
    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
726
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
727
    QString lineContents(int line) const; // 1 based line
hjk's avatar
hjk committed
728
    void setLineContents(int line, const QString &contents); // 1 based line
hjk's avatar
hjk committed
729

hjk's avatar
hjk committed
730
731
    int linesOnScreen() const;
    int columnsOnScreen() const;
732
733
    int linesInDocument() const;

hjk's avatar
hjk committed
734
    // The following use all zero-based counting.
735
    int cursorLineOnScreen() const;
736
737
738
739
740
741
742
743
    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
744
745
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
746

747
    CursorPosition cursorPosition() const
748
        { return CursorPosition(position(), firstVisibleLine()); }
749
    void setCursorPosition(const CursorPosition &p)
750
        { setPosition(p.position); scrollToLine(p.scrollLine); }
751

hjk's avatar
hjk committed
752
    // Helper functions for indenting/
753
    bool isElectricCharacter(QChar c) const;
754
    void indentSelectedText(QChar lastTyped = QChar());
755
    void indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
756
757
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
758

hjk's avatar
hjk committed
759
    void moveToFirstNonBlankOnLine();
760
    void moveToTargetColumn();
761
    void setTargetColumn() {
762
        m_targetColumn = logicalCursorColumn();
763
        m_visualTargetColumn = m_targetColumn;
764
765
        //qDebug() << "TARGET: " << m_targetColumn;
    }
766
    void moveToNextWord(bool simple, bool deleteWord = false);
767
    void moveToMatchingParanthesis();
768
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
769

hjk's avatar
hjk committed
770
    // Convenience wrappers to reduce line noise.
771
    void moveToStartOfLine();
hjk's avatar
hjk committed
772
    void moveToEndOfLine();
hjk's avatar
hjk committed
773
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
774
    void moveUp(int n = 1) { moveDown(-n); }
hjk's avatar
hjk committed
775
    void moveDown(int n = 1);
hjk's avatar
hjk committed
776
777
778
779
    void dump(const char *msg) const {
        qDebug() << msg << "POS: " << anchor() << position()
            << "EXT: " << m_oldExternalAnchor << m_oldExternalPosition
            << "INT: " << m_oldInternalAnchor << m_oldInternalPosition
hjk's avatar
hjk committed
780
            << "VISUAL: " << m_visualMode;
hjk's avatar
hjk committed
781
782
783
    }
    void moveRight(int n = 1) {
        //dump("RIGHT 1");
hjk's avatar
hjk committed
784
785
786
        QTextCursor tc = cursor();
        tc.movePosition(Right, KeepAnchor, n);
        setCursor(tc);
hjk's avatar
hjk committed
787
788
789
        //dump("RIGHT 2");
    }
    void moveLeft(int n = 1) {
hjk's avatar
hjk committed
790
791
792
        QTextCursor tc = cursor();
        tc.movePosition(Left, KeepAnchor, n);
        setCursor(tc);
hjk's avatar
hjk committed
793
    }
hjk's avatar
hjk committed
794
    void setAnchor() {
hjk's avatar
hjk committed
795
796
797
        QTextCursor tc = cursor();
        tc.setPosition(tc.position(), MoveAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
798
799
    }
    void setAnchor(int position) {
hjk's avatar
hjk committed
800
801
802
803
        QTextCursor tc = cursor();
        tc.setPosition(tc.anchor(), MoveAnchor);
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
804
805
    }
    void setPosition(int position) {
hjk's avatar
hjk committed
806
807
808
        QTextCursor tc = cursor();
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
809
    }
hjk's avatar
hjk committed
810
    void setAnchorAndPosition(int anchor, int position) {
hjk's avatar
hjk committed
811
812
813
814
        QTextCursor tc = cursor();
        tc.setPosition(anchor, MoveAnchor);
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
815
816
817
    }
    QTextCursor cursor() const { return EDITOR(textCursor()); }
    void setCursor(const QTextCursor &tc) { EDITOR(setTextCursor(tc)); }
hjk's avatar
hjk committed
818

819
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
820

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

824
    void enterInsertMode();
825
    void enterReplaceMode();
826
    void enterCommandMode();
827
    void enterExMode();
hjk's avatar
hjk committed
828
829
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
830
    void notImplementedYet();
831
832
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
833
    void updateCursorShape();
834
    QWidget *editor() const;
835
    QTextDocument *document() const { return EDITOR(document()); }
836
    QChar characterAtCursor() const
hjk's avatar
hjk committed
837
838
839
840
841
842
843
844
845
        { return document()->characterAt(position()); }
    void beginEditBlock()
        { UNDO_DEBUG("BEGIN EDIT BLOCK"); cursor().beginEditBlock(); }
    void beginEditBlock(int pos)
        { setUndoPosition(pos); cursor().beginEditBlock(); }
    void endEditBlock()
        { UNDO_DEBUG("END EDIT BLOCK"); cursor().endEditBlock(); }
    void joinPreviousEditBlock()
        { UNDO_DEBUG("JOIN"); cursor().joinPreviousEditBlock(); }
hjk's avatar
hjk committed
846
847
    void breakEditBlock() {
        QTextCursor tc = cursor();
hjk's avatar
hjk committed
848
        tc.clearSelection();
hjk's avatar
hjk committed
849
850
851
852
853
854
        tc.beginEditBlock();
        tc.insertText("x");
        tc.deletePreviousChar();
        tc.endEditBlock();
        setCursor(tc);
    }
855

856
857
858
859
860
    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
861
    void updateEditor();
862

863
864
865
866
867
868
869
    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);

870
    Q_SLOT void importSelection();
871
    void exportSelection();
872
    void insertInInsertMode(const QString &text);
873

874
875
876
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
877
    bool m_wasReadOnly; // saves read-only state of document
878

hjk's avatar
hjk committed
879
880
    FakeVimHandler *q;
    Mode m_mode;
881
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
882
    SubMode m_submode;
hjk's avatar
hjk committed
883
    SubSubMode m_subsubmode;
884
    Input m_subsubdata;
hjk's avatar
hjk committed
885
886
887
888
    int m_oldExternalPosition; // copy from last event to check for external changes
    int m_oldExternalAnchor;
    int m_oldInternalPosition; // copy from last event to check for external changes
    int m_oldInternalAnchor;
889
    int m_oldPosition; // FIXME: Merge with above.
hjk's avatar
hjk committed
890
    int m_register;
hjk's avatar
hjk committed
891
892
    QString m_mvcount;
    QString m_opcount;
893
894
    MoveType m_movetype;
    RangeMode m_rangemode;
895
    int m_visualInsertCount;
hjk's avatar
hjk committed
896
897

    bool m_fakeEnd;
hjk's avatar
hjk committed
898
899
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
900

hjk's avatar
hjk committed
901
    int m_gflag;  // whether current command started with 'g'
902

903
    QString m_commandPrefix;
904
    CommandBuffer m_commandBuffer;
905
906
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
907

908
    bool m_lastSearchForward;
909
    bool m_findPending;
910
    int m_findStartPosition;
hjk's avatar
hjk committed
911
    QString m_lastInsertion;
912
    QString m_lastDeletion;
hjk's avatar
hjk committed
913

hjk's avatar
hjk committed
914
915
    int anchor() const { return cursor().anchor(); }
    int position() const { return cursor().position(); }
916

917
918
919
920
921
922
923
924
925
926
927
    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());
928

hjk's avatar
hjk committed
929
    void insertText(const Register &reg);
930
    void removeText(const Range &range);
931
    void removeTransform(TransformationData *td);
932

hjk's avatar
hjk committed
933
    void invertCase(const Range &range);
934
    void invertCaseTransform(TransformationData *td);
935

hjk's avatar
hjk committed
936
    void upCase(const Range &range);
937
    void upCaseTransform(TransformationData *td);
938

hjk's avatar
hjk committed
939
    void downCase(const Range &range);
940
    void downCaseTransform(TransformationData *td);
941

hjk's avatar
hjk committed
942
    void replaceText(const Range &range, const QString &str);
943
944
    void replaceByStringTransform(TransformationData *td);
    void replaceByCharTransform(TransformationData *td);
945

hjk's avatar
hjk committed
946
947
948
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
949
    Range rangeFromCurrentLine() const;
950
951
952
953

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

    void pasteText(bool afterCursor);
954

955
    // undo handling
956
957
    void undo();
    void redo();
958
    void setUndoPosition(int pos);
959
    QMap<int, int> m_undoCursorPosition; // revision -> position
960

hjk's avatar
hjk committed
961
    // extra data for '.'
hjk's avatar
hjk committed
962
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
963
964
    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
965

hjk's avatar
hjk committed
966
967
    // extra data for ';'
    QString m_semicolonCount;
968
969
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
970

971
972
    // visual modes
    void toggleVisualMode(VisualMode visualMode);
973
974
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
975
    VisualMode m_oldVisualMode;
hjk's avatar
hjk committed
976

977
    // marks as lines
hjk's avatar
hjk committed
978
979
    int mark(int code) const;
    void setMark(int code, int position);
980
981
982
    typedef QHash<int, QTextCursor> Marks;
    typedef QHashIterator<int, QTextCursor> MarksIterator;
    Marks m_marks;
983

hjk's avatar
hjk committed
984
    // vi style configuration
hjk's avatar
hjk committed
985
986
987
988
    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); }
989

990
991
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
992

hjk's avatar
hjk committed
993
    // auto-indent
994
    QString tabExpand(int len) const;
995
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
996
997
998
999
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
1000
    void handleStartOfLine();
hjk's avatar
hjk committed
1001

1002
    void recordJump();
1003
1004
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;