fakevimhandler.cpp 158 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
//   A current "region of interest"
hjk's avatar
hjk committed
55
56
//   spans between anchor(), (i.e. the character below anchor()), and
//   position(). The character below position() is not included
57
//   if the last movement command was exclusive (MoveExclusive).
hjk's avatar
hjk committed
58
//
59

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

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

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

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

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

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

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

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

namespace FakeVim {
namespace Internal {

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

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

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

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

using namespace Qt;

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

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

hjk's avatar
hjk committed
145
146
147
/*! 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
148
149
150
enum SubMode
{
    NoSubMode,
hjk's avatar
hjk committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    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
166
};
hjk's avatar
hjk committed
167

hjk's avatar
hjk committed
168
169
170
/*! 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
171
172
173
enum SubSubMode
{
    NoSubSubMode,
hjk's avatar
hjk committed
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.
182
    SearchSubSubMode,
hjk's avatar
hjk committed
183
184
};

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

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

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

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

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

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

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

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

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

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

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


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

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

287
QString Range::toString() const
hjk's avatar
hjk committed
288
{
289
290
291
292
293
294
295
296
297
298
299
300
    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)
301
    : cmd(c), hasBang(false), args(a), range(r), count(1)
302
{}
hjk's avatar
hjk committed
303

304
305
306
307
308
bool ExCommand::matches(const QString &min, const QString &full) const
{
    return cmd.startsWith(min) && full.startsWith(cmd);
}

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

314
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
315
{
316
    foreach (const QTextEdit::ExtraSelection &sel, sels)
317
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
318
319
    return ts;
}
320

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

337
338
339
340
341
342
343
344
345
346
347
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;
}

348
inline QString msgMarkNotSet(const QString &text)
349
{
hjk's avatar
hjk committed
350
    return FakeVimHandler::tr("Mark '%1' not set").arg(text);
351
352
}

hjk's avatar
hjk committed
353
class Input
hjk's avatar
hjk committed
354
{
hjk's avatar
hjk committed
355
public:
356
357
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
358

359
360
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
361

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

380
381
382
383
384
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

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

hjk's avatar
hjk committed
395
396
397
398
399
400
    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

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

    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
421
422
        return a.m_key == m_key && a.m_modifiers == m_modifiers
            && m_text == a.m_text;
423
424
    }

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

427
428
    QString text() const { return m_text; }

hjk's avatar
hjk committed
429
430
431
432
433
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

434
435
    int key() const { return m_key; }

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

hjk's avatar
hjk committed
457
458
459
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
460
{
hjk's avatar
hjk committed
461
462
463
464
465
466
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};

467

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

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

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

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

hjk's avatar
hjk committed
583

hjk's avatar
hjk committed
584
585
586
587
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
588
public:
589
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
590

591
592
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
593
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
594
    void handleExCommand(const QString &cmd);
595

hjk's avatar
hjk committed
596
    // Updates marks positions by the difference in positionChange.
597
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
598

599
    void installEventFilter();
600
    void passShortcuts(bool enable);
601
    void setupWidget();
602
    void restoreWidget(int tabSize);
603

604
    friend class FakeVimHandler;
hjk's avatar
hjk committed
605
606

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

hjk's avatar
hjk committed
626
627
628
    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
629
630
631
    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
632
633
    bool atBlockEnd() const { return cursor().atBlockEnd(); }
    bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
hjk's avatar
hjk committed
634

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

hjk's avatar
hjk committed
642
643
    int linesOnScreen() const;
    int columnsOnScreen() const;
644
645
    int linesInDocument() const;

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

659
    CursorPosition cursorPosition() const
660
        { return CursorPosition(position(), firstVisibleLine()); }
661
    void setCursorPosition(const CursorPosition &p)
662
        { setPosition(p.position); scrollToLine(p.scrollLine); }
663

hjk's avatar
hjk committed
664
    // Helper functions for indenting/
665
    bool isElectricCharacter(QChar c) const;
666
    void indentSelectedText(QChar lastTyped = QChar());
667
    void indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
668
669
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
670

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

hjk's avatar
hjk committed
682
    // Convenience wrappers to reduce line noise.
683
    void moveToStartOfLine();
hjk's avatar
hjk committed
684
    void moveToEndOfLine();
hjk's avatar
hjk committed
685
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
686
    void moveUp(int n = 1) { moveDown(-n); }
hjk's avatar
hjk committed
687
    void moveDown(int n = 1);
hjk's avatar
hjk committed
688
689
690
691
    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
692
            << "VISUAL: " << m_visualMode;
hjk's avatar
hjk committed
693
694
695
    }
    void moveRight(int n = 1) {
        //dump("RIGHT 1");
hjk's avatar
hjk committed
696
697
698
        QTextCursor tc = cursor();
        tc.movePosition(Right, KeepAnchor, n);
        setCursor(tc);
hjk's avatar
hjk committed
699
700
701
        //dump("RIGHT 2");
    }
    void moveLeft(int n = 1) {
hjk's avatar
hjk committed
702
703
704
        QTextCursor tc = cursor();
        tc.movePosition(Left, KeepAnchor, n);
        setCursor(tc);
hjk's avatar
hjk committed
705
    }
hjk's avatar
hjk committed
706
    void setAnchor() {
hjk's avatar
hjk committed
707
708
709
        QTextCursor tc = cursor();
        tc.setPosition(tc.position(), MoveAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
710
711
    }
    void setAnchor(int position) {
hjk's avatar
hjk committed
712
713
714
715
        QTextCursor tc = cursor();
        tc.setPosition(tc.anchor(), MoveAnchor);
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
716
717
    }
    void setPosition(int position) {
hjk's avatar
hjk committed
718
719
720
        QTextCursor tc = cursor();
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
721
    }
hjk's avatar
hjk committed
722
    void setAnchorAndPosition(int anchor, int position) {
hjk's avatar
hjk committed
723
724
725
726
        QTextCursor tc = cursor();
        tc.setPosition(anchor, MoveAnchor);
        tc.setPosition(position, KeepAnchor);
        setCursor(tc);
hjk's avatar
hjk committed
727
728
729
    }
    QTextCursor cursor() const { return EDITOR(textCursor()); }
    void setCursor(const QTextCursor &tc) { EDITOR(setTextCursor(tc)); }
hjk's avatar
hjk committed
730

731
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
732

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

736
    void enterInsertMode();
737
    void enterReplaceMode();
738
    void enterCommandMode();
739
    void enterExMode();
hjk's avatar
hjk committed
740
741
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
742
    void notImplementedYet();
743
744
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
745
    void updateCursorShape();
746
    QWidget *editor() const;
747
    QTextDocument *document() const { return EDITOR(document()); }
748
    QChar characterAtCursor() const
hjk's avatar
hjk committed
749
750
751
752
753
754
755
756
757
        { 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
758
759
    void breakEditBlock() {
        QTextCursor tc = cursor();
hjk's avatar
hjk committed
760
        tc.clearSelection();
hjk's avatar
hjk committed
761
762
763
764
765
766
        tc.beginEditBlock();
        tc.insertText("x");
        tc.deletePreviousChar();
        tc.endEditBlock();
        setCursor(tc);
    }
767

768
769
770
771
772
    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
773
    void updateEditor();
774

775
776
777
778
779
780
781
    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);

782
    Q_SLOT void importSelection();
783
    void exportSelection();
784
    void insertInInsertMode(const QString &text);
785

786
787
788
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
789
    bool m_wasReadOnly; // saves read-only state of document
790

hjk's avatar
hjk committed
791
792
    FakeVimHandler *q;
    Mode m_mode;
793
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
794
    SubMode m_submode;
hjk's avatar
hjk committed
795
    SubSubMode m_subsubmode;
796
    Input m_subsubdata;
hjk's avatar
hjk committed
797
798
799
800
    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;
hjk's avatar
hjk committed
801
    int m_register;
hjk's avatar
hjk committed
802
803
    QString m_mvcount;
    QString m_opcount;
804
805
    MoveType m_movetype;
    RangeMode m_rangemode;
806
    int m_visualInsertCount;
hjk's avatar
hjk committed
807
808

    bool m_fakeEnd;
hjk's avatar
hjk committed
809
810
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
811

hjk's avatar
hjk committed
812
    int m_gflag;  // whether current command started with 'g'
813

814
    QString m_commandPrefix;
hjk's avatar
hjk committed
815
    QString m_commandBuffer;
816
817
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
818

819
    bool m_lastSearchForward;
820
    bool m_findPending;
hjk's avatar
hjk committed
821
    QString m_lastInsertion;
822
    QString m_lastDeletion;
hjk's avatar
hjk committed
823

hjk's avatar
hjk committed
824
825
    int anchor() const { return cursor().anchor(); }
    int position() const { return cursor().position(); }
826

827
828
829
830
831
832
833
834
835
836
837
    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());
838

hjk's avatar
hjk committed
839
    void insertText(const Register &reg);
840
    void removeText(const Range &range);
841
    void removeTransform(TransformationData *td);
842

hjk's avatar
hjk committed
843
    void invertCase(const Range &range);
844
    void invertCaseTransform(TransformationData *td);
845

hjk's avatar
hjk committed
846
    void upCase(const Range &range);
847
    void upCaseTransform(TransformationData *td);
848

hjk's avatar
hjk committed
849
    void downCase(const Range &range);
850
    void downCaseTransform(TransformationData *td);
851

hjk's avatar
hjk committed
852
    void replaceText(const Range &range, const QString &str);
853
854
    void replaceByStringTransform(TransformationData *td);
    void replaceByCharTransform(TransformationData *td);
855

hjk's avatar
hjk committed
856
857
858
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
859
860
861
862

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

    void pasteText(bool afterCursor);
863

864
    // undo handling
865
866
    void undo();
    void redo();
867
    void setUndoPosition(int pos);
868
    QMap<int, int> m_undoCursorPosition; // revision -> position
869

hjk's avatar
hjk committed
870
    // extra data for '.'
hjk's avatar
hjk committed
871
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
872
873
    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
874

hjk's avatar
hjk committed
875
876
    // extra data for ';'
    QString m_semicolonCount;
877
878
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
879

hjk's avatar
hjk committed
880
    // visual line mode
881
882
883
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
884
    VisualMode m_oldVisualMode;
hjk's avatar
hjk committed
885

886
    // marks as lines
hjk's avatar
hjk committed
887
888
    int mark(int code) const;
    void setMark(int code, int position);
889
890
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
891
    // vi style configuration
hjk's avatar
hjk committed
892
893
894
895
    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); }
896

897
898
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
899
    int m_cursorWidth;
900

hjk's avatar
hjk committed
901
    // auto-indent
902
    QString tabExpand(int len) const;
903
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
904
905
906
907
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
908
    void handleStartOfLine();
hjk's avatar
hjk committed
909

910
    void recordJump();
911
912
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
913
914

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
915
    QTextCursor m_searchCursor;
hjk's avatar
hjk committed
916
    QString m_oldNeedle;
hjk's avatar
hjk committed
917

hjk's avatar
hjk committed
918
    bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
919
    bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
hjk's avatar
hjk committed
920
921
922
923
    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
924
    bool handleExRegisterCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
925
    bool handleExMapCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
926
    bool handleExNohlsearchCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
927
928
929
930
    bool handleExNormalCommand(const ExCommand &cmd);
    bool handleExReadCommand(const ExCommand &cmd);
    bool handleExRedoCommand(const ExCommand &cmd);
    bool handleExSetCommand(const ExCommand &cmd);
931
    bool handleExShiftCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
932
933
934
    bool handleExSourceCommand(const ExCommand &cmd);
    bool handleExSubstituteCommand(const ExCommand &cmd);
    bool handleExWriteCommand(const ExCommand &cmd);