fakevimhandler.cpp 152 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
hjk's avatar
hjk committed
8
**
9
** Commercial Usage
hjk's avatar
hjk committed
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
hjk's avatar
hjk committed
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
hjk's avatar
hjk committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

30
//
31
32
33
34
35
36
37
38
39
40
41
// ATTENTION:
//
// 1 Please do not add any direct dependencies to other Qt Creator code here.
//   Instead emit signals and let the FakeVimPlugin channel the information to
//   Qt Creator. The idea is to keep this file here in a "clean" state that
//   allows easy reuse with any QTextEdit or QPlainTextEdit derived class.
//
// 2 There are a few auto tests located in ../../../tests/auto/fakevim.
//   Commands that are covered there are marked as "// tested" below.
//
// 3 Some conventions:
42
//
43
44
45
//   Use 1 based line numbers and 0 based column numbers. Even though
//   the 1 based line are not nice it matches vim's and QTextEdit's 'line'
//   concepts.
46
//
47
48
49
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
50
51
52
53
//   A QTextCursor is always between characters, whereas vi's cursor is always
//   over a character. FakeVim interprets the QTextCursor to be over the character
//   to the right of the QTextCursor's position().
//
54
//   There is always a "current" cursor (m_tc). A current "region of interest"
55
56
57
//   spans between m_anchor (== anchor()), i.e. the character below anchor()), and
//   m_tc.position() (== position()). The character below position() is not included
//   if the last movement command was exclusive (MoveExclusive).
58
//   The value of m_tc.anchor() is not used.
hjk's avatar
hjk committed
59
//
60

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

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

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

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

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

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

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

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

namespace FakeVim {
namespace Internal {

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

hjk's avatar
hjk committed
117
118
119
120
121
122
123
124
125
126
#define StartOfLine     QTextCursor::StartOfLine
#define EndOfLine       QTextCursor::EndOfLine
#define MoveAnchor      QTextCursor::MoveAnchor
#define KeepAnchor      QTextCursor::KeepAnchor
#define Up              QTextCursor::Up
#define Down            QTextCursor::Down
#define Right           QTextCursor::Right
#define Left            QTextCursor::Left
#define EndOfDocument   QTextCursor::End
#define StartOfDocument QTextCursor::Start
hjk's avatar
hjk committed
127

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

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

using namespace Qt;

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

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

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

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

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

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

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

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

    \value RangeCharMode   Entered by pressing \key v. The range includes
                           all characters between cursor and anchor.
    \value RangeLineMode   Entered by pressing \key V. The range includes
                           all lines between the line of the cursor and
                           the line of the anchor.
    \value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
                           newline when deleting.
    \value RangeBlockMode  Entered by pressing \key Ctrl-v. The range includes
                           all characters with line and column coordinates
                           between line and columns coordinates of cursor and
                           anchor.
    \value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
                           all characters in the affected lines up to the end
                           of these lines.
*/
220
221
222
223
224
225
226
227

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

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

235
236
237
238
239
240
241
242
243
244
245
246
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
247
    Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
248
249
250
251
252
    Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
    QString contents;
    RangeMode rangemode;
};

hjk's avatar
hjk committed
253
254
255
256
257
QDebug operator<<(QDebug ts, const Register &reg)
{
    return ts << reg.contents;
}

hjk's avatar
hjk committed
258
259
260
261
262
263
264
265
266
267
268
269
270
struct SearchData
{
    SearchData() { init(); }

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

    QString needle;
    bool forward;
    bool mustMove;
    bool highlightMatches;
    bool highlightCursor;
};
271
272


273
274
275
Range::Range()
    : beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
276

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

281
QString Range::toString() const
hjk's avatar
hjk committed
282
{
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    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
297
298
299

QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
300
    return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
hjk's avatar
hjk committed
301
302
}

303
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
304
{
305
    foreach (const QTextEdit::ExtraSelection &sel, sels)
306
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
307
308
    return ts;
}
309

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

326
327
328
329
330
331
332
333
334
335
336
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;
}

337
inline QString msgMarkNotSet(const QString &text)
338
{
hjk's avatar
hjk committed
339
    return FakeVimHandler::tr("Mark '%1' not set").arg(text);
340
341
}

hjk's avatar
hjk committed
342
class Input
hjk's avatar
hjk committed
343
{
hjk's avatar
hjk committed
344
public:
345
346
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
347

348
349
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
350

hjk's avatar
hjk committed
351
    Input(int k, int m, const QString &t)
352
353
354
355
356
357
358
359
360
361
        : m_key(k), m_modifiers(m), m_text(t)
    {
        // m_xkey is only a cache.
        m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
    }

    bool isDigit() const
    {
        return m_xkey >= '0' && m_xkey <= '9';
    }
362

363
364
365
366
367
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

368
369
370
371
372
373
374
375
376
377
    bool isBackspace() const
    {
        return m_key == Key_Backspace || isControl('h');
    }

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

hjk's avatar
hjk committed
378
379
380
381
382
383
    bool isEscape() const
    {
        return isKey(Key_Escape) || isKey(27) || isControl('c')
            || isControl(Key_BracketLeft);
    }

384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
    bool is(int c) const
    {
        return m_xkey == c && (m_modifiers == 0 || m_modifiers == Qt::ShiftModifier);
    }

    bool isControl(int c) const
    {
        return m_modifiers == Qt::ControlModifier &&
            (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
    }

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

    bool operator==(const Input &a) const
    {
hjk's avatar
hjk committed
402
403
        return a.m_key == m_key && a.m_modifiers == m_modifiers
            && m_text == a.m_text;
404
405
    }

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

408
409
    QString text() const { return m_text; }

hjk's avatar
hjk committed
410
411
412
413
414
    QChar asChar() const
    {
        return (m_text.size() == 1 ? m_text.at(0) : QChar());
    }

415
416
    int key() const { return m_key; }

hjk's avatar
hjk committed
417
418
419
420
421
    QDebug dump(QDebug ts) const
    {
        return ts << m_key << '-' << m_modifiers << '-'
            << quoteUnprintable(m_text);
    }
422
423
424
425
426
427
private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
428

hjk's avatar
hjk committed
429
430
431
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }

class Inputs : public QVector<Input>
432
{
hjk's avatar
hjk committed
433
434
435
436
437
438
public:
    Inputs() {}
    explicit Inputs(const QString &str) { parseFrom(str); }
    void parseFrom(const QString &str);
};

439

hjk's avatar
hjk committed
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
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
468

hjk's avatar
hjk committed
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
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
489
// Mappings for a specific mode.
hjk's avatar
hjk committed
490
class ModeMapping : public QList<QPair<Inputs, Inputs> >
hjk's avatar
hjk committed
491
492
493
494
495
496
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
497
498
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
    }

    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
520
521
    // 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
522
    // and replace *input with the mapped data.
hjk's avatar
hjk committed
523
    bool mappingDone(Inputs *inputs) const
hjk's avatar
hjk committed
524
525
526
    {
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
hjk's avatar
hjk committed
527
            const Inputs &haystack = at(i).first;
528
            // A mapping
hjk's avatar
hjk committed
529
530
            if (startsWith(haystack, *inputs)) {
                if (haystack.size() != inputs->size())
hjk's avatar
hjk committed
531
532
                    return false; // This can be extended.
                // Actual mapping.
hjk's avatar
hjk committed
533
                *inputs = at(i).second;
hjk's avatar
hjk committed
534
535
536
                return true;
            }
        }
hjk's avatar
hjk committed
537
        // No extensible mapping found. Use inputs as-is.
hjk's avatar
hjk committed
538
539
540
541
542
543
544
545
546
547
        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
548
            if (needle.at(i) != haystack.at(i))
hjk's avatar
hjk committed
549
550
551
552
553
554
                return false;
        }
        return true;
    }
};

hjk's avatar
hjk committed
555

hjk's avatar
hjk committed
556
557
558
559
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
560
public:
561
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
562

563
564
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
565
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
566
    void handleExCommand(const QString &cmd);
567

hjk's avatar
hjk committed
568
    // Updates marks positions by the difference in positionChange.
569
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
570

571
    void installEventFilter();
572
    void passShortcuts(bool enable);
573
    void setupWidget();
574
    void restoreWidget(int tabSize);
575

576
    friend class FakeVimHandler;
hjk's avatar
hjk committed
577
578

    void init();
hjk's avatar
hjk committed
579
580
581
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
582
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
583
584
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
585
586
    EventResult handleExMode(const Input &);
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
587
    EventResult handleCommandSubSubMode(const Input &);
588
589
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
590
    void resetCommandMode();
hjk's avatar
hjk committed
591
    void search(const SearchData &sd);
hjk's avatar
hjk committed
592
    void highlightMatches(const QString &needle);
593
    void stopIncrementalFind();
hjk's avatar
hjk committed
594

hjk's avatar
hjk committed
595
596
597
    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
598
599
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
600
601
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
602

hjk's avatar
hjk committed
603
    int lastPositionInDocument() const; // Returns last valid position in doc.
604
605
    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
606
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
607
    QString lineContents(int line) const; // 1 based line
hjk's avatar
hjk committed
608
    void setLineContents(int line, const QString &contents); // 1 based line
hjk's avatar
hjk committed
609

hjk's avatar
hjk committed
610
611
    int linesOnScreen() const;
    int columnsOnScreen() const;
612
613
    int linesInDocument() const;

hjk's avatar
hjk committed
614
    // The following use all zero-based counting.
615
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
616
    int cursorLineInDocument() const;
617
618
619
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
620
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
621
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
622
623
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
624

625
626
627
628
629
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

hjk's avatar
hjk committed
630
    // Helper functions for indenting/
631
    bool isElectricCharacter(QChar c) const;
632
633
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
634
635
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
636

hjk's avatar
hjk committed
637
    void moveToFirstNonBlankOnLine();
638
    void moveToTargetColumn();
639
640
    void setTargetColumn() {
        m_targetColumn = leftDist();
641
        m_visualTargetColumn = m_targetColumn;
642
643
        //qDebug() << "TARGET: " << m_targetColumn;
    }
644
    void moveToNextWord(bool simple, bool deleteWord = false);
645
    void moveToMatchingParanthesis();
646
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
647

hjk's avatar
hjk committed
648
    // Convenience wrappers to reduce line noise.
hjk's avatar
hjk committed
649
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
650
    void moveToStartOfLine();
hjk's avatar
hjk committed
651
    void moveToEndOfLine();
hjk's avatar
hjk committed
652
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
653
654
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
655
656
    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
657
    void setAnchor();
658
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
659
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
660

661
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
662

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

666
    void enterInsertMode();
667
    void enterReplaceMode();
668
    void enterCommandMode();
669
    void enterExMode();
hjk's avatar
hjk committed
670
671
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
672
    void notImplementedYet();
673
674
    void updateMiniBuffer();
    void updateSelection();
675
    void updateCursor();
676
    QWidget *editor() const;
677
678
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
679
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
680
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
681
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
682
683
684
685
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN"); m_tc.joinPreviousEditBlock(); }
    void breakEditBlock()
        { m_tc.beginEditBlock(); m_tc.insertText("x");
          m_tc.deletePreviousChar(); m_tc.endEditBlock(); }
686

687
688
689
690
691
    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
692
    void updateEditor();
693

694
695
696
697
698
699
700
    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);

701
702
    Q_SLOT void importSelection();

703
704
705
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
706
    bool m_wasReadOnly; // saves read-only state of document
707

hjk's avatar
hjk committed
708
709
    FakeVimHandler *q;
    Mode m_mode;
710
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
711
    SubMode m_submode;
hjk's avatar
hjk committed
712
    SubSubMode m_subsubmode;
713
    Input m_subsubdata;
hjk's avatar
hjk committed
714
    QTextCursor m_tc;
715
    int m_oldPosition; // copy from last event to check for external changes
716
    int m_anchor;
hjk's avatar
hjk committed
717
    int m_register;
hjk's avatar
hjk committed
718
719
    QString m_mvcount;
    QString m_opcount;
720
721
    MoveType m_movetype;
    RangeMode m_rangemode;
722
    int m_visualInsertCount;
hjk's avatar
hjk committed
723
724

    bool m_fakeEnd;
hjk's avatar
hjk committed
725
726
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
727

hjk's avatar
hjk committed
728
    int m_gflag;  // whether current command started with 'g'
729

730
    QString m_commandPrefix;
hjk's avatar
hjk committed
731
    QString m_commandBuffer;
732
733
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
734

735
    bool m_lastSearchForward;
736
    bool m_findPending;
hjk's avatar
hjk committed
737
    QString m_lastInsertion;
738
    QString m_lastDeletion;
hjk's avatar
hjk committed
739

hjk's avatar
hjk committed
740
741
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
742

743
744
745
746
747
748
749
750
751
752
753
    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());
754

hjk's avatar
hjk committed
755
    void insertText(const Register &reg);
756
    void removeText(const Range &range);
757
    void removeTransform(TransformationData *td);
758

hjk's avatar
hjk committed
759
    void invertCase(const Range &range);
760
    void invertCaseTransform(TransformationData *td);
761

hjk's avatar
hjk committed
762
    void upCase(const Range &range);
763
    void upCaseTransform(TransformationData *td);
764

hjk's avatar
hjk committed
765
    void downCase(const Range &range);
766
    void downCaseTransform(TransformationData *td);
767

768
    QString m_replacement;
hjk's avatar
hjk committed
769
    void replaceText(const Range &range, const QString &str);
770
    void replaceTransform(TransformationData *td);
771

hjk's avatar
hjk committed
772
773
774
    QString selectText(const Range &range) const;
    void setCurrentRange(const Range &range);
    Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
775
776
777
778

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

    void pasteText(bool afterCursor);
779

780
    // undo handling
781
782
    void undo();
    void redo();
783
    void setUndoPosition(int pos);
784
    QMap<int, int> m_undoCursorPosition; // revision -> position
785

hjk's avatar
hjk committed
786
    // extra data for '.'
hjk's avatar
hjk committed
787
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
788
789
    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
790

hjk's avatar
hjk committed
791
792
    // extra data for ';'
    QString m_semicolonCount;
793
794
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
795

hjk's avatar
hjk committed
796
    // visual line mode
797
798
799
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
800

801
    // marks as lines
hjk's avatar
hjk committed
802
803
    int mark(int code) const;
    void setMark(int code, int position);
804
805
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
806
    // vi style configuration
hjk's avatar
hjk committed
807
808
809
810
    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); }
811

812
813
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
814
    int m_cursorWidth;
815

hjk's avatar
hjk committed
816
    // auto-indent
817
    QString tabExpand(int len) const;
818
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
819
820
821
822
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
823
    void handleStartOfLine();
hjk's avatar
hjk committed
824

825
    void recordJump();
826
827
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
828
829

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
830
    QTextCursor m_searchCursor;
hjk's avatar
hjk committed
831
    QString m_oldNeedle;
hjk's avatar
hjk committed
832

hjk's avatar
hjk committed
833
    bool handleExCommandHelper(const ExCommand &cmd); // Returns success.
834
    bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
hjk's avatar
hjk committed
835
836
837
838
    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
839
    bool handleExRegisterCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
840
841
842
843
844
    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);
845
    bool handleExShiftCommand(const ExCommand &cmd);
hjk's avatar
hjk committed
846
847
848
    bool handleExSourceCommand(const ExCommand &cmd);
    bool handleExSubstituteCommand(const ExCommand &cmd);
    bool handleExWriteCommand(const ExCommand &cmd);
849

hjk's avatar
hjk committed
850
    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
851
852
853
854

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

    static struct GlobalData
    {
858
859
860
861
862
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
        }
hjk's avatar
hjk committed
863
864

        // Input.
hjk's avatar
hjk committed
865
        Inputs pendingInput;
hjk's avatar
hjk committed
866
867
868
869
870
871
        int inputTimer;

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

hjk's avatar
hjk committed
872
873
874
875
876
        // History for searches.
        History searchHistory;

        // History for :ex commands.
        History commandHistory;
hjk's avatar
hjk committed
877
878
879
880
881
882
883

        QHash<int, Register> registers;

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

hjk's avatar
hjk committed
886
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
887

888
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
889
{
hjk's avatar
hjk committed
890
    //static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
891
    q = parent;
892
893
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
894
    //new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
895
896
    init();
}
897

hjk's avatar
hjk committed
898
899
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
900
    m_mode = CommandMode;
hjk's avatar
hjk committed
901
902
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
903
    m_passing = false;
904
    m_findPending = false;
hjk's avatar
hjk committed
905
    m_fakeEnd = false;
906
    m_positionPastEnd = m_anchorPastEnd = false;
907
    m_lastSearchForward = true;
hjk's avatar
hjk committed
908
    m_register = '"';
hjk's avatar
hjk committed
909
    m_gflag = false;
910
    m_visualMode = NoVisualMode;
911
    m_targetColumn = 0;
912
    m_visualTargetColumn = 0;
913
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
914
    m_anchor = 0;
915
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
916
    m_justAutoIndented = 0;
917
    m_rangemode = RangeCharMode;
hjk's avatar
hjk committed
918
919

    setupCharClass();
hjk's avatar
hjk committed
920
921
}

922
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
923
{
924
925
926
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
927

928
    if (key == Key_Escape) {
929
        if (m_subsubmode == SearchSubSubMode)
930
            return true;
hjk's avatar
hjk committed
931
        // Not sure this feels good. People often hit Esc several times
932
933
934
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
935
936
937
    }

    // We are interested in overriding  most Ctrl key combinations
938
939
940
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
941
        // Ctrl-K is special as it is the Core's default notion of Locator
942
943
944
945
946
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
947
        }
948
949
950
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
951
952
    }

953
954
955
956
957
958
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
959
    const int key = ev->key();
960
961
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
962
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
963
964
965
966
967
968
969
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
970
        passShortcuts(false);
971
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
972
        //if (input.is(',')) { // use ',,' to leave, too.
973
974
975
976
977
978
979
980
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
981
982