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

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

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

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

hjk's avatar
hjk committed
66
#include <QtCore/QDebug>
67
#include <QtCore/QFile>
hjk's avatar
hjk committed
68
#include <QtCore/QObject>
69
#include <QtCore/QPointer>
hjk's avatar
hjk committed
70
#include <QtCore/QProcess>
hjk's avatar
hjk committed
71
#include <QtCore/QRegExp>
72
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
73
#include <QtCore/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() << << m_tc.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
130
131
132
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

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

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

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

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

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

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

hjk's avatar
hjk committed
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*!
    \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.
*/
218
219
enum RangeMode
{
hjk's avatar
hjk committed
220
221
222
223
    RangeCharMode,         // v
    RangeLineMode,         // V
    RangeLineModeExclusive,
    RangeBlockMode,        // Ctrl-v
224
    RangeBlockAndTailMode, // Ctrl-v for D and X
225
226
227
228
229
230
231
232
233
};

enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

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

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

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

    Range(int b, int e, RangeMode m = RangeCharMode)
        : beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
con's avatar
con committed
266
    {}
267

268
    QString toString() const
269
270
271
272
273
    {
        return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
            .arg(rangemode);
    }

274
275
276
277
278
    int beginPos;
    int endPos;
    RangeMode rangemode;
};

279
280
QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
{
281
    foreach (const QTextEdit::ExtraSelection &sel, sels)
282
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
283
284
    return ts;
}
285

hjk's avatar
hjk committed
286
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
287
{
hjk's avatar
hjk committed
288
289
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
290
291
        const QChar c = ba.at(i);
        const int cc = c.unicode();
hjk's avatar
hjk committed
292
293
        if (c.isPrint())
            res += c;
294
295
        else if (cc == '\n')
            res += QLatin1String("<CR>");
hjk's avatar
hjk committed
296
        else
297
            res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
hjk's avatar
hjk committed
298
299
    }
    return res;
hjk's avatar
hjk committed
300
301
}

302
303
304
305
306
307
308
309
310
311
312
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;
}

313
314
315
316
317
inline QString msgE20MarkNotSet(const QString &text)
{
    return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text);
}

hjk's avatar
hjk committed
318
class Input
hjk's avatar
hjk committed
319
{
hjk's avatar
hjk committed
320
public:
321
322
    Input()
        : m_key(0), m_xkey(0), m_modifiers(0) {}
hjk's avatar
hjk committed
323

324
325
    explicit Input(QChar x)
        : m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x) {}
hjk's avatar
hjk committed
326

327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
    Input(int k, int m, QString t)
        : 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';
    }
    bool isKey(int c) const
    {
        return !m_modifiers && m_key == c;
    }

    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
    {
        return a.m_key == m_key && m_text == a.m_text;
    }

    QString text() const { return m_text; }

    int key() const { return m_key; }

private:
    int m_key;
    int m_xkey;
    int m_modifiers;
    QString m_text;
};
hjk's avatar
hjk committed
374

375
376
377
378
379
QDebug &operator<<(QDebug &ts, const Input &input)
{
    return ts << input.text() << input.key();
}

hjk's avatar
hjk committed
380
381
382
383
384
385
386
387
388
389
typedef QVector<Input> Inputs;

// Mappings for a specific mode.
class ModeMapping : private QList<QPair<Inputs, Inputs> >
{
public:
    ModeMapping() { test(); }

    void test()
    {
hjk's avatar
hjk committed
390
391
        //insert(Inputs() << Input('A') << Input('A'),
        //       Inputs() << Input('x') << Input('x'));
hjk's avatar
hjk committed
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
    }

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

413
    // Returns 'false' if more input input is needed to decide whether a
hjk's avatar
hjk committed
414
415
416
417
418
419
420
    // mapping needs to be applied. If a decision can be made, return 'true',
    // and replace *input with the mapped data.
    bool mappingDone(Inputs *input) const
    {
        Q_UNUSED(input);
        // FIXME: inefficient.
        for (int i = 0; i != size(); ++i) {
421
            // A mapping
hjk's avatar
hjk committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
            if (startsWith(at(i).first, *input)) {
                if (at(i).first.size() != input->size())
                    return false; // This can be extended.
                // Actual mapping.
                *input = at(i).second;
                return true;
            }
        }
        // No extensible mapping found. Use input as-is.
        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) {
441
            if (needle.at(i).text() != haystack.at(i).text())
hjk's avatar
hjk committed
442
443
444
445
446
447
                return false;
        }
        return true;
    }
};

hjk's avatar
hjk committed
448

hjk's avatar
hjk committed
449
450
451
452
class FakeVimHandler::Private : public QObject
{
    Q_OBJECT

hjk's avatar
hjk committed
453
public:
454
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
455

456
457
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
458
    void handleCommand(const QString &cmd); // Sets m_tc + handleExCommand
459
    void handleExCommand(const QString &cmd);
460
461
462

    // updates marks positions by the difference in positionChange
    void fixMarks(int positionAction, int positionChange);
hjk's avatar
hjk committed
463

464
    void installEventFilter();
465
    void passShortcuts(bool enable);
466
    void setupWidget();
467
    void restoreWidget(int tabSize);
468

469
    friend class FakeVimHandler;
hjk's avatar
hjk committed
470
471

    void init();
hjk's avatar
hjk committed
472
473
474
    EventResult handleKey(const Input &);
    Q_SLOT EventResult handleKey2();
    EventResult handleInsertMode(const Input &);
475
    EventResult handleReplaceMode(const Input &);
hjk's avatar
hjk committed
476
477
    EventResult handleCommandMode(const Input &);
    EventResult handleRegisterMode(const Input &);
hjk's avatar
hjk committed
478
479
    EventResult handleExMode(const Input &);
    EventResult handleSearchSubSubMode(const Input &);
hjk's avatar
hjk committed
480
    EventResult handleCommandSubSubMode(const Input &);
481
482
    void finishMovement(const QString &dotCommand = QString());
    void finishMovement(const QString &dotCommand, int count);
483
    void resetCommandMode();
hjk's avatar
hjk committed
484
    void search(const QString &needle, bool forward, bool incSearch = false);
hjk's avatar
hjk committed
485
    void highlightMatches(const QString &needle);
486
    void stopIncrementalFind();
hjk's avatar
hjk committed
487

hjk's avatar
hjk committed
488
489
490
    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
491
492
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
493
494
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
495

hjk's avatar
hjk committed
496
    int lastPositionInDocument() const; // last valid pos in doc
497
498
    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
499
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
500
501
    QString lineContents(int line) const; // 1 based line
    void setLineContents(int line, const QString &contents) const; // 1 based line
hjk's avatar
hjk committed
502

hjk's avatar
hjk committed
503
504
    int linesOnScreen() const;
    int columnsOnScreen() const;
505
506
507
508
    int linesInDocument() const;

    // all zero-based counting
    int cursorLineOnScreen() const;
hjk's avatar
hjk committed
509
    int cursorLineInDocument() const;
510
511
512
    int physicalCursorColumnInDocument() const; // as stored in the data
    int logicalCursorColumnInDocument() const; // as visible on screen
    Column cursorColumnInDocument() const; // as visible on screen
513
    int firstVisibleLineInDocument() const;
hjk's avatar
hjk committed
514
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
515
516
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
517

518
519
520
521
522
    CursorPosition cursorPosition() const
        { return CursorPosition(position(), firstVisibleLineInDocument()); }
    void setCursorPosition(const CursorPosition &p)
        { setPosition(p.position); scrollToLineInDocument(p.scrollLine); }

523
    // helper functions for indenting
524
    bool isElectricCharacter(QChar c) const;
525
526
    void indentSelectedText(QChar lastTyped = QChar());
    int indentText(const Range &range, QChar lastTyped = QChar());
hjk's avatar
hjk committed
527
528
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
529

hjk's avatar
hjk committed
530
    void moveToFirstNonBlankOnLine();
531
    void moveToTargetColumn();
532
533
    void setTargetColumn() {
        m_targetColumn = leftDist();
534
        m_visualTargetColumn = m_targetColumn;
535
536
        //qDebug() << "TARGET: " << m_targetColumn;
    }
537
    void moveToNextWord(bool simple, bool deleteWord = false);
538
    void moveToMatchingParanthesis();
539
    void moveToWordBoundary(bool simple, bool forward, bool changeWord = false);
hjk's avatar
hjk committed
540
541

    // to reduce line noise
hjk's avatar
hjk committed
542
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
543
    void moveToStartOfLine();
hjk's avatar
hjk committed
544
    void moveToEndOfLine();
hjk's avatar
hjk committed
545
    void moveBehindEndOfLine();
hjk's avatar
hjk committed
546
547
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
548
549
    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
550
    void setAnchor();
551
    void setAnchor(int position) { if (!isVisualMode()) m_anchor = position; }
552
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
553

554
    bool handleFfTt(QString key);
hjk's avatar
hjk committed
555

hjk's avatar
hjk committed
556
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
557
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
558
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
559

560
    void enterInsertMode();
561
    void enterReplaceMode();
562
    void enterCommandMode();
563
    void enterExMode();
hjk's avatar
hjk committed
564
565
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
566
    void notImplementedYet();
567
568
    void updateMiniBuffer();
    void updateSelection();
569
    void updateCursor();
570
    QWidget *editor() const;
571
572
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
573
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
574
    void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
hjk's avatar
hjk committed
575
576
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
577

578
579
580
    // this asks the layer above (e.g. the fake vim plugin or the
    // stand-alone test application to handle the command)
    void passUnknownExCommand(const QString &cmd);
581
582
583
    // this asks the layer above (e.g. the fake vim plugin or the
    // stand-alone test application to handle the set command)
    void passUnknownSetCommand(const QString &cmd);
584

585
586
587
588
589
    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
590
    void updateEditor();
591

592
593
594
595
596
597
598
    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);

599
600
601
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
602
    bool m_wasReadOnly; // saves read-only state of document
603

hjk's avatar
hjk committed
604
605
    FakeVimHandler *q;
    Mode m_mode;
606
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
607
    SubMode m_submode;
hjk's avatar
hjk committed
608
    SubSubMode m_subsubmode;
609
    Input m_subsubdata;
hjk's avatar
hjk committed
610
    QTextCursor m_tc;
611
    int m_oldPosition; // copy from last event to check for external changes
612
    int m_anchor;
hjk's avatar
hjk committed
613
    int m_register;
hjk's avatar
hjk committed
614
615
    QString m_mvcount;
    QString m_opcount;
616
617
    MoveType m_movetype;
    RangeMode m_rangemode;
618
    int m_visualInsertCount;
hjk's avatar
hjk committed
619
620

    bool m_fakeEnd;
hjk's avatar
hjk committed
621
622
    bool m_anchorPastEnd;
    bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
hjk's avatar
hjk committed
623

hjk's avatar
hjk committed
624
    int m_gflag;  // whether current command started with 'g'
625

626
    QString m_commandPrefix;
hjk's avatar
hjk committed
627
    QString m_commandBuffer;
628
629
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
630

631
    bool m_lastSearchForward;
632
    bool m_findPending;
hjk's avatar
hjk committed
633
    QString m_lastInsertion;
634
    QString m_lastDeletion;
hjk's avatar
hjk committed
635

hjk's avatar
hjk committed
636
637
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
638

hjk's avatar
hjk committed
639
640
    typedef void (FakeVimHandler::Private::*Transformation)(int, QTextCursor *);
    void transformText(const Range &range, Transformation transformation);
641

hjk's avatar
hjk committed
642
    void removeSelectedText();
643
    void removeText(const Range &range);
644
645
646
647
648
649
650
651
652
653
    void removeTransform(int, QTextCursor *);

    void invertCaseSelectedText();
    void invertCaseTransform(int, QTextCursor *);

    void upCaseSelectedText();
    void upCaseTransform(int, QTextCursor *);

    void downCaseSelectedText();
    void downCaseTransform(int, QTextCursor *);
654

655
656
    QString m_replacement;
    void replaceSelectedText(); // replace each character with m_replacement
657
658
    void replaceTransform(int, QTextCursor *);

659
660
661
662
663
664
665
    QString selectedText() const { return text(Range(position(), anchor())); }
    QString text(const Range &range) const;

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

    void pasteText(bool afterCursor);
666

667
    // undo handling
668
669
    void undo();
    void redo();
670
    void setUndoPosition(int pos);
671
    QMap<int, int> m_undoCursorPosition; // revision -> position
672
    bool m_beginEditBlock;
673

hjk's avatar
hjk committed
674
    // extra data for '.'
hjk's avatar
hjk committed
675
    void replay(const QString &text, int count);
hjk's avatar
hjk committed
676
677
    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
678

hjk's avatar
hjk committed
679
680
    // extra data for ';'
    QString m_semicolonCount;
681
682
    Input m_semicolonType;  // 'f', 'F', 't', 'T'
    QString m_semicolonKey;
hjk's avatar
hjk committed
683

hjk's avatar
hjk committed
684
    // history for '/'
hjk's avatar
hjk committed
685
    QString lastSearchString() const;
hjk's avatar
hjk committed
686

hjk's avatar
hjk committed
687
    // visual line mode
688
689
690
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
691

692
693
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
694
    QString m_oldNeedle;
695

hjk's avatar
hjk committed
696
    // vi style configuration
hjk's avatar
hjk committed
697
698
699
700
    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); }
701

702
703
    int m_targetColumn; // -1 if past end of line
    int m_visualTargetColumn; // 'l' can move past eol in visual mode only
704

705
    int m_cursorWidth;
706

hjk's avatar
hjk committed
707
    // auto-indent
708
    QString tabExpand(int len) const;
709
    Column indentation(const QString &line) const;
hjk's avatar
hjk committed
710
711
712
713
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
714
    void handleStartOfLine();
hjk's avatar
hjk committed
715

716
    void recordJump();
717
    void recordNewUndo();
718
719
    QVector<CursorPosition> m_jumpListUndo;
    QVector<CursorPosition> m_jumpListRedo;
hjk's avatar
hjk committed
720
721

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
722

723
    bool handleExCommandHelper(const QString &cmd); // Returns success.
724
    QString extractCommand(const QString &line, int *beginLine, int *endLine);
725
726
    bool handleExBangCommand(const QString &line);
    bool handleExDeleteCommand(const QString &line);
727
    bool handleExGotoCommand(const QString &line);
728
729
730
    bool handleExHistoryCommand(const QString &line);
    bool handleExMapCommand(const QString &line);
    bool handleExNormalCommand(const QString &line);
731
732
733
    bool handleExReadCommand(const QString &line);
    bool handleExRedoCommand(const QString &line);
    bool handleExSetCommand(const QString &line);
734
735
    bool handleExShiftRightCommand(const QString &line);
    bool handleExSourceCommand(const QString &line);
736
    bool handleExSubstituteCommand(const QString &line);
737
    bool handleExWriteCommand(const QString &line);
738

hjk's avatar
hjk committed
739
    void timerEvent(QTimerEvent *ev);
hjk's avatar
hjk committed
740
741
742
743

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

    static struct GlobalData
    {
747
748
749
750
751
752
753
        GlobalData()
        {
            inReplay = false;
            inputTimer = -1;
            searchHistoryIndex = 0;
            commandHistoryIndex = 0;
        }
hjk's avatar
hjk committed
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774

        // Input.
        QVector<Input> pendingInput;
        int inputTimer;

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

        // Histories.
        QStringList searchHistory;
        int searchHistoryIndex;
        QStringList commandHistory;
        int commandHistoryIndex;

        QHash<int, Register> registers;

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

hjk's avatar
hjk committed
777
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
778

779
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
780
{
hjk's avatar
hjk committed
781
    //static PythonHighlighterRules pythonRules;
hjk's avatar
hjk committed
782
    q = parent;
783
784
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
785
    //new Highlighter(EDITOR(document()), &pythonRules);
hjk's avatar
hjk committed
786
787
    init();
}
788

hjk's avatar
hjk committed
789
790
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
791
    m_mode = CommandMode;
hjk's avatar
hjk committed
792
793
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
794
    m_passing = false;
795
    m_findPending = false;
hjk's avatar
hjk committed
796
    m_fakeEnd = false;
797
    m_positionPastEnd = m_anchorPastEnd = false;
798
    m_lastSearchForward = true;
hjk's avatar
hjk committed
799
    m_register = '"';
hjk's avatar
hjk committed
800
    m_gflag = false;
801
    m_visualMode = NoVisualMode;
802
    m_targetColumn = 0;
803
    m_visualTargetColumn = 0;
804
    m_movetype = MoveInclusive;
hjk's avatar
hjk committed
805
    m_anchor = 0;
806
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
807
    m_justAutoIndented = 0;
808
    m_rangemode = RangeCharMode;
809
    m_beginEditBlock = true;
hjk's avatar
hjk committed
810
811

    setupCharClass();
hjk's avatar
hjk committed
812
813
}

814
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
815
{
816
817
818
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
819

820
    if (key == Key_Escape) {
821
        if (m_subsubmode == SearchSubSubMode)
822
            return true;
hjk's avatar
hjk committed
823
        // Not sure this feels good. People often hit Esc several times
824
825
826
        if (isNoVisualMode() && m_mode == CommandMode)
            return false;
        return true;
827
828
829
    }

    // We are interested in overriding  most Ctrl key combinations
830
831
832
    if (mods == Qt::ControlModifier
            && ((key >= Key_A && key <= Key_Z && key != Key_K)
                || key == Key_BracketLeft || key == Key_BracketRight)) {
con's avatar
con committed
833
        // Ctrl-K is special as it is the Core's default notion of Locator
834
835
836
837
838
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
839
        }
840
841
842
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
843
844
    }

845
846
847
848
849
850
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
851
    const int key = ev->key();
852
853
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
854
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
855
856
857
858
859
860
861
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
862
        passShortcuts(false);
863
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
864
        //if (input.is(',')) { // use ',,' to leave, too.
865
866
867
868
869
870
871
872
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
873
874

    // Fake "End of line"
875
876
    m_tc = EDITOR(textCursor());

877
878
    // Position changed externally
    if (m_tc.position() != m_oldPosition) {
879
        setTargetColumn();
880
881
882
        if (m_mode == InsertMode) {
            int dist = m_tc.position() - m_oldPosition;
            // Try to compensate for code completion
883
            if (dist > 0 && dist <= physicalCursorColumnInDocument()) {
884
885
886
                Range range(m_oldPosition, m_tc.position());
                m_lastInsertion.append(text(range));
            }
887
888
889
        } else if (!isVisualMode()) {
            if (atEndOfLine())
                moveLeft();
890
891
        }
    }
892
893

    m_tc.setVisualNavigation(true);
con's avatar
con committed
894

hjk's avatar
hjk committed
895
    if (m_fakeEnd)
hjk's avatar
hjk committed
896
        moveRight();
hjk's avatar
hjk committed
897

898
899
900
901
902
903
904
    //if ((mods & Qt::ControlModifier) != 0) {
    //    if (key >= Key_A && key <= Key_Z)
    //        key = shift(key); // make it lower case
    //    key = control(key);
    //} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
    //    key = shift(key);
    //}
905

906
907
    QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
            || !m_tc.atBlockEnd() || m_tc.block().length() <= 1,
hjk's avatar
hjk committed
908
        qDebug() << "Cursor at EOL before key handler");
909

910
    EventResult result = handleKey(Input(key, mods, ev->text()));
hjk's avatar
hjk committed
911

912
913
914
915
    // the command might have destroyed the editor
    if (m_textedit || m_plaintextedit) {
        // We fake vi-style end-of-line behaviour
        m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
hjk's avatar
hjk committed
916

917
918
        QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
                || !m_tc.atBlockEnd() || m_tc.block().length() <= 1,
hjk's avatar
hjk committed
919
            qDebug() << "Cursor at EOL after key handler");
920

921
922
        if (m_fakeEnd)
            moveLeft();
hjk's avatar
hjk committed
923

924
925
926
        EDITOR(setTextCursor(m_tc));
        m_oldPosition = m_tc.position();
    }
927
    return result;
hjk's avatar
hjk committed
928
929
}

930
931
932
933
934
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

935
936
937
938
939
940
941
942
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
943
    m_wasReadOnly = EDITOR(isReadOnly());
944

hjk's avatar
hjk committed
945
946
    updateEditor();

947
948
949
950
951
952
953
954
955
956
957
958
959
960
    QTextCursor tc = EDITOR(textCursor());
    if (tc.hasSelection()) {
        int pos = tc.position();
        int anc = tc.anchor();
        m_marks['<'] = anc;
        m_marks['>'] = pos;
        m_anchor = anc;
        m_visualMode = VisualCharMode;
        tc.clearSelection();
        EDITOR(setTextCursor(tc));
        m_tc = tc; // needed in updateSelection
        updateSelection();
    }

961
    updateMiniBuffer();
962
    updateCursor();
963
964
}

hjk's avatar
hjk committed
965
966
967
968
void FakeVimHandler::Private::updateEditor()
{
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
hjk's avatar
hjk committed
969
970

    setupCharClass();
hjk's avatar
hjk committed
971
972
}

973
void FakeVimHandler::Private::restoreWidget(int tabSize)
974
975
976
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
977
    //EDITOR(removeEventFilter(q));
978
    //EDITOR(setReadOnly(m_wasReadOnly));
979
980
    const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
    EDITOR(setTabStopWidth(charWidth * tabSize));
981

982
    if (isVisualLineMode()) {