fakevimhandler.cpp 84.5 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
7
8
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
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
26
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
hjk's avatar
hjk committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

30
#include "fakevimhandler.h"
hjk's avatar
hjk committed
31

32
//
33
34
35
36
37
38
39
40
41
42
43
// 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:
44
//
45
46
47
//   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.
48
//
49
50
51
52
53
54
55
//   Do not pass QTextCursor etc around unless really needed. Convert
//   early to  line/column.
//
//   There is always a "current" cursor (m_tc). A current "region of interest"
//   spans between m_anchor (== anchor()) and  m_tc.position() (== position())
//   The value of m_tc.anchor() is not used.
// 
56

hjk's avatar
hjk committed
57
#include <utils/qtcassert.h>
58

hjk's avatar
hjk committed
59
#include <QtCore/QDebug>
60
#include <QtCore/QFile>
hjk's avatar
hjk committed
61
#include <QtCore/QObject>
62
#include <QtCore/QPointer>
hjk's avatar
hjk committed
63
#include <QtCore/QProcess>
hjk's avatar
hjk committed
64
#include <QtCore/QRegExp>
65
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
66
#include <QtCore/QtAlgorithms>
hjk's avatar
hjk committed
67
68
#include <QtCore/QStack>

hjk's avatar
hjk committed
69
#include <QtGui/QApplication>
hjk's avatar
hjk committed
70
71
72
73
74
75
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
76
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
77
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
78
79


80
81
82
83
84
85
86
//#define DEBUG_KEY  1
#if DEBUG_KEY
#   define KEY_DEBUG(s) qDebug() << s
#else
#   define KEY_DEBUG(s)
#endif

87
88
//#define DEBUG_UNDO  1
#if DEBUG_UNDO
hjk's avatar
hjk committed
89
#   define UNDO_DEBUG(s) qDebug() << << m_tc.document()->revision() << s
90
91
92
93
#else
#   define UNDO_DEBUG(s)
#endif

hjk's avatar
hjk committed
94
95
96
97
98
using namespace Core::Utils;

namespace FakeVim {
namespace Internal {

hjk's avatar
hjk committed
99
100
101
102
103
104
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////

hjk's avatar
hjk committed
105
106
107
108
109
110
111
112
113
114
#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
115

116
117
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
118
119
120
121
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

122

hjk's avatar
hjk committed
123
124
125
126
enum Mode
{
    InsertMode,
    CommandMode,
127
128
129
    ExMode,
    SearchForwardMode,
    SearchBackwardMode,
hjk's avatar
hjk committed
130
};
hjk's avatar
hjk committed
131

hjk's avatar
hjk committed
132
133
134
enum SubMode
{
    NoSubMode,
135
136
137
    ChangeSubMode,     // used for c
    DeleteSubMode,     // used for d
    FilterSubMode,     // used for !
hjk's avatar
hjk committed
138
139
    IndentSubMode,     // used for =
    RegisterSubMode,   // used for "
hjk's avatar
hjk committed
140
    ReplaceSubMode,    // used for R and r
141
142
    ShiftLeftSubMode,  // used for <
    ShiftRightSubMode, // used for >
hjk's avatar
hjk committed
143
144
    WindowSubMode,     // used for Ctrl-w
    YankSubMode,       // used for y
145
146
    ZSubMode,          // used for z
    CapitalZSubMode    // used for Z
hjk's avatar
hjk committed
147
};
hjk's avatar
hjk committed
148

hjk's avatar
hjk committed
149
150
enum SubSubMode
{
hjk's avatar
hjk committed
151
152
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
153
    NoSubSubMode,
154
155
    FtSubSubMode,       // used for f, F, t, T
    MarkSubSubMode,     // used for m
156
    BackTickSubSubMode, // used for `
hjk's avatar
hjk committed
157
    TickSubSubMode,     // used for '
hjk's avatar
hjk committed
158
159
};

160
161
162
163
164
165
166
167
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

hjk's avatar
hjk committed
168
169
170
171
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
hjk's avatar
hjk committed
172
    MoveLineWise,
hjk's avatar
hjk committed
173
174
};

175
176
QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
{
177
178
    foreach (QTextEdit::ExtraSelection sel, sels)
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
179
180
    return ts;
}
181

hjk's avatar
hjk committed
182
QString quoteUnprintable(const QString &ba)
hjk's avatar
hjk committed
183
{
hjk's avatar
hjk committed
184
185
186
187
188
189
190
191
192
    QString res;
    for (int i = 0, n = ba.size(); i != n; ++i) {
        QChar c = ba.at(i);
        if (c.isPrint())
            res += c;
        else
            res += QString("\\x%1").arg(c.unicode(), 2, 16);
    }
    return res;
hjk's avatar
hjk committed
193
194
}

195
196
197
198
199
200
201
enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

hjk's avatar
hjk committed
202
203
204
class FakeVimHandler::Private
{
public:
205
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
206

207
208
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
hjk's avatar
hjk committed
209
    void handleCommand(const QString &cmd); // sets m_tc + handleExCommand
210
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
211

212
    void installEventFilter();
213
214
215
    void setupWidget();
    void restoreWidget();

216
    friend class FakeVimHandler;
hjk's avatar
hjk committed
217
218
219
220
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
221
222
223
224
225
    EventResult handleKey(int key, int unmodified, const QString &text);
    EventResult handleInsertMode(int key, int unmodified, const QString &text);
    EventResult handleCommandMode(int key, int unmodified, const QString &text);
    EventResult handleRegisterMode(int key, int unmodified, const QString &text);
    EventResult handleMiniBufferModes(int key, int unmodified, const QString &text);
hjk's avatar
hjk committed
226
    void finishMovement(const QString &text = QString());
hjk's avatar
hjk committed
227
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
228
    void highlightMatches(const QString &needle);
hjk's avatar
hjk committed
229

hjk's avatar
hjk committed
230
231
232
    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
233
234
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
235
236
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
237

hjk's avatar
hjk committed
238
    int lastPositionInDocument() const;
239
240
    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
241
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
hjk's avatar
hjk committed
242

hjk's avatar
hjk committed
243
244
245
246
247
248
    // all zero-based counting
    int cursorLineOnScreen() const;
    int linesOnScreen() const;
    int columnsOnScreen() const;
    int cursorLineInDocument() const;
    int cursorColumnInDocument() const;
hjk's avatar
hjk committed
249
    int linesInDocument() const;
hjk's avatar
hjk committed
250
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
251
252
    void scrollUp(int count);
    void scrollDown(int count) { scrollUp(-count); }
hjk's avatar
hjk committed
253

254
    // helper functions for indenting
hjk's avatar
hjk committed
255
    bool isElectricCharacter(QChar c) const
256
        { return c == '{' || c == '}' || c == '#'; }
hjk's avatar
hjk committed
257
258
259
    void indentRegion(QChar lastTyped = QChar());
    void shiftRegionLeft(int repeat = 1);
    void shiftRegionRight(int repeat = 1);
260

hjk's avatar
hjk committed
261
    void moveToFirstNonBlankOnLine();
262
    void moveToTargetColumn();
263
264
265
266
    void setTargetColumn() {
        m_targetColumn = leftDist();
        //qDebug() << "TARGET: " << m_targetColumn;
    }
hjk's avatar
hjk committed
267
    void moveToNextWord(bool simple);
268
    void moveToMatchingParanthesis();
hjk's avatar
hjk committed
269
    void moveToWordBoundary(bool simple, bool forward);
hjk's avatar
hjk committed
270
271

    // to reduce line noise
hjk's avatar
hjk committed
272
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
273
    void moveToStartOfLine();
hjk's avatar
hjk committed
274
275
276
    void moveToEndOfLine();
    void moveUp(int n = 1) { moveDown(-n); }
    void moveDown(int n = 1); // { m_tc.movePosition(Down, MoveAnchor, n); }
hjk's avatar
hjk committed
277
278
    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
279
    void setAnchor() { m_anchor = m_tc.position(); }
280
281
    void setAnchor(int position) { m_anchor = position; }
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
282

hjk's avatar
hjk committed
283
    void handleFfTt(int key);
hjk's avatar
hjk committed
284

hjk's avatar
hjk committed
285
    // helper function for handleExCommand. return 1 based line index.
hjk's avatar
hjk committed
286
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
287
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
288

289
    void enterInsertMode();
290
    void enterCommandMode();
291
    void enterExMode();
hjk's avatar
hjk committed
292
293
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
294
    void notImplementedYet();
295
296
    void updateMiniBuffer();
    void updateSelection();
297
    QWidget *editor() const;
298
299
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
hjk's avatar
hjk committed
300
301
302
    void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
    void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
    void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
303

304
305
306
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
307
    bool m_wasReadOnly; // saves read-only state of document
308

hjk's avatar
hjk committed
309
310
    FakeVimHandler *q;
    Mode m_mode;
311
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
312
    SubMode m_submode;
hjk's avatar
hjk committed
313
314
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
315
316
    QString m_input;
    QTextCursor m_tc;
317
    QTextCursor m_oldTc; // copy from last event to check for external changes
318
    int m_anchor;
hjk's avatar
hjk committed
319
320
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
321
322
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
323
    MoveType m_moveType;
hjk's avatar
hjk committed
324
325
326

    bool m_fakeEnd;

327
328
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
329
    int m_gflag;  // whether current command started with 'g'
330

hjk's avatar
hjk committed
331
    QString m_commandBuffer;
332
333
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
334

335
    bool m_lastSearchForward;
hjk's avatar
hjk committed
336
    QString m_lastInsertion;
hjk's avatar
hjk committed
337

338
    QString removeSelectedText();
hjk's avatar
hjk committed
339
340
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
341
342
    QString selectedText() const;

343
    // undo handling
344
345
    void undo();
    void redo();
346
    QMap<int, int> m_undoCursorPosition; // revision -> position
347

hjk's avatar
hjk committed
348
    // extra data for '.'
hjk's avatar
hjk committed
349
    void replay(const QString &text, int count);
350
351
    void setDotCommand(const QString &cmd) { m_dotCommand = cmd; }
    void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); }
hjk's avatar
hjk committed
352
    QString m_dotCommand;
hjk's avatar
hjk committed
353
    bool m_inReplay; // true if we are executing a '.'
hjk's avatar
hjk committed
354

hjk's avatar
hjk committed
355
356
357
358
359
    // extra data for ';'
    QString m_semicolonCount;
    int m_semicolonType;  // 'f', 'F', 't', 'T'
    int m_semicolonKey;

hjk's avatar
hjk committed
360
    // history for '/'
hjk's avatar
hjk committed
361
    QString lastSearchString() const;
362
    static QStringList m_searchHistory;
hjk's avatar
hjk committed
363
364
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
365
    // history for ':'
366
    static QStringList m_commandHistory;
hjk's avatar
hjk committed
367
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
368

hjk's avatar
hjk committed
369
    // visual line mode
370
371
372
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
373

374
375
    // marks as lines
    QHash<int, int> m_marks;
hjk's avatar
hjk committed
376
    QString m_oldNeedle;
377

hjk's avatar
hjk committed
378
    // vi style configuration
hjk's avatar
hjk committed
379
380
381
382
    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); }
383
384

    // for restoring cursor position
hjk's avatar
hjk committed
385
    int m_savedYankPosition;
386
    int m_targetColumn;
387

388
    int m_cursorWidth;
389

hjk's avatar
hjk committed
390
391
392
393
394
    // auto-indent
    void insertAutomaticIndentation(bool goingDown);
    bool removeAutomaticIndentation(); // true if something removed
    // number of autoindented characters
    int m_justAutoIndented;
395
    void handleStartOfLine();
hjk's avatar
hjk committed
396

397
    void recordJump();
398
    void recordNewUndo();
399
400
    QList<int> m_jumpListUndo;
    QList<int> m_jumpListRedo;
hjk's avatar
hjk committed
401
402

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
403
404
};

405
406
407
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;

408
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
409
410
{
    q = parent;
411
412
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
hjk's avatar
hjk committed
413
414
    init();
}
415

hjk's avatar
hjk committed
416
417
void FakeVimHandler::Private::init()
{
hjk's avatar
hjk committed
418
    m_mode = CommandMode;
hjk's avatar
hjk committed
419
420
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
421
    m_passing = false;
hjk's avatar
hjk committed
422
    m_fakeEnd = false;
423
    m_lastSearchForward = true;
hjk's avatar
hjk committed
424
    m_register = '"';
hjk's avatar
hjk committed
425
    m_gflag = false;
426
    m_visualMode = NoVisualMode;
427
    m_targetColumn = 0;
hjk's avatar
hjk committed
428
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
429
430
    m_anchor = 0;
    m_savedYankPosition = 0;
431
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
432
    m_inReplay = false;
hjk's avatar
hjk committed
433
    m_justAutoIndented = 0;
hjk's avatar
hjk committed
434
435
}

436
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
437
{
438
439
440
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
441

442
    if (key == Key_Escape) {
hjk's avatar
hjk committed
443
        // Not sure this feels good. People often hit Esc several times
444
445
446
447
448
449
450
451
452
453
454
455
456
        if (m_visualMode == NoVisualMode && m_mode == CommandMode)
            return false;
        return true;
    }

    // We are interested in overriding  most Ctrl key combinations
    if (mods == Qt::ControlModifier && key >= Key_A && key <= Key_Z && key != Key_K) {
        // Ctrl-K is special as it is the Core's default notion of QuickOpen
        if (m_passing) {
            KEY_DEBUG(" PASSING CTRL KEY");
            // We get called twice on the same key
            //m_passing = false;
            return false;
457
        }
458
459
460
        KEY_DEBUG(" NOT PASSING CTRL KEY");
        //updateMiniBuffer();
        return true;
461
462
    }

463
464
465
466
467
468
469
470
471
472
    // Let other shortcuts trigger
    return false;
}

EventResult FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
{
    int key = ev->key();
    const int um = key; // keep unmodified key around
    const int mods = ev->modifiers();

hjk's avatar
hjk committed
473
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

    if (m_passing) {
        KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
        //if (key == ',') { // use ',,' to leave, too.
        //    qDebug() << "FINISHED...";
        //    return EventHandled;
        //}
        m_passing = false;
        updateMiniBuffer();
        KEY_DEBUG("   PASS TO CORE");
        return EventPassedToCore;
    }
hjk's avatar
hjk committed
491
492

    // Fake "End of line"
493
494
    m_tc = EDITOR(textCursor());

495
496
497
498
499
    if (m_tc.position() != m_oldTc.position())
        setTargetColumn();

    m_tc.setVisualNavigation(true);
    
hjk's avatar
hjk committed
500
    if (m_fakeEnd)
hjk's avatar
hjk committed
501
        moveRight();
hjk's avatar
hjk committed
502

503
    if ((mods & Qt::ControlModifier) != 0) {
hjk's avatar
hjk committed
504
        key += 256;
505
        key += 32; // make it lower case
506
    } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
507
508
        key += 32;
    }
509

hjk's avatar
hjk committed
510
    m_undoCursorPosition[m_tc.document()->revision()] = m_tc.position();
hjk's avatar
hjk committed
511
512
513
514
    //if (m_mode == InsertMode)
    //    joinPreviousEditBlock();
    //else
    //    beginEditBlock();
515
    EventResult result = handleKey(key, um, ev->text());
hjk's avatar
hjk committed
516
    //endEditBlock();
hjk's avatar
hjk committed
517
518

    // We fake vi-style end-of-line behaviour
519
    m_fakeEnd = (atEndOfLine() && m_mode == CommandMode);
hjk's avatar
hjk committed
520

hjk's avatar
hjk committed
521
    if (m_fakeEnd)
hjk's avatar
hjk committed
522
        moveLeft();
hjk's avatar
hjk committed
523

524
    m_oldTc = m_tc;
525
    EDITOR(setTextCursor(m_tc));
526
    return result;
hjk's avatar
hjk committed
527
528
}

529
530
531
532
533
void FakeVimHandler::Private::installEventFilter()
{
    EDITOR(installEventFilter(q));
}

534
535
536
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
537
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
538
539
540
541
542
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
543
    m_wasReadOnly = EDITOR(isReadOnly());
544
    //EDITOR(setReadOnly(true));
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559

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

560
    //showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
561
562
563
564
565
566
567
    updateMiniBuffer();
}

void FakeVimHandler::Private::restoreWidget()
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
568
    //EDITOR(removeEventFilter(q));
569
    EDITOR(setReadOnly(m_wasReadOnly));
570
571
    EDITOR(setCursorWidth(m_cursorWidth));
    EDITOR(setOverwriteMode(false));
572

573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
    if (m_visualMode == VisualLineMode) {
        m_tc = EDITOR(textCursor());
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
        m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor);
        m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    } else if (m_visualMode == VisualCharMode) {
        m_tc = EDITOR(textCursor());
        m_tc.setPosition(m_marks['<'], MoveAnchor);
        m_tc.setPosition(m_marks['>'], KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    }

    m_visualMode = NoVisualMode;
    updateSelection();
589
590
}

591
592
EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
593
{
594
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
hjk's avatar
hjk committed
595
    if (m_mode == InsertMode)
596
        return handleInsertMode(key, unmodified, text);
597
    if (m_mode == CommandMode)
598
        return handleCommandMode(key, unmodified, text);
599
    if (m_mode == ExMode || m_mode == SearchForwardMode
600
            || m_mode == SearchBackwardMode)
601
        return handleMiniBufferModes(key, unmodified, text);
602
    return EventUnhandled;
hjk's avatar
hjk committed
603
604
}

hjk's avatar
hjk committed
605
606
void FakeVimHandler::Private::moveDown(int n)
{
hjk's avatar
hjk committed
607
608
609
610
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(Down, MoveAnchor, n);
#else
hjk's avatar
hjk committed
611
    const int col = m_tc.position() - m_tc.block().position();
612
613
614
    const int lastLine = m_tc.document()->lastBlock().blockNumber();
    const int targetLine = qMax(0, qMin(lastLine, m_tc.block().blockNumber() + n));
    const QTextBlock &block = m_tc.document()->findBlockByNumber(targetLine);
hjk's avatar
hjk committed
615
616
617
    const int pos = block.position();
    setPosition(pos + qMin(block.length() - 1, col));
    moveToTargetColumn();
hjk's avatar
hjk committed
618
#endif
hjk's avatar
hjk committed
619
620
621
622
}

void FakeVimHandler::Private::moveToEndOfLine()
{
hjk's avatar
hjk committed
623
624
625
626
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(EndOfLine, MoveAnchor);
#else
hjk's avatar
hjk committed
627
628
    const QTextBlock &block = m_tc.block();
    setPosition(block.position() + block.length() - 1);
hjk's avatar
hjk committed
629
#endif
hjk's avatar
hjk committed
630
631
}

632
633
634
635
636
637
638
639
640
641
642
void FakeVimHandler::Private::moveToStartOfLine()
{
#if 0
    // does not work for "hidden" documents like in the autotests
    m_tc.movePosition(StartOfLine, MoveAnchor);
#else
    const QTextBlock &block = m_tc.block();
    setPosition(block.position());
#endif
}

hjk's avatar
hjk committed
643
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
644
{
645
    //qDebug() << "ANCHOR: " << position() << anchor();
hjk's avatar
hjk committed
646
    if (m_submode == FilterSubMode) {
hjk's avatar
hjk committed
647
648
        int beginLine = lineForPosition(anchor());
        int endLine = lineForPosition(position());
649
        setPosition(qMin(anchor(), position()));
650
        enterExMode();
hjk's avatar
hjk committed
651
        m_currentMessage.clear();
hjk's avatar
hjk committed
652
653
654
655
656
657
658
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

659
660
661
    if (m_visualMode != NoVisualMode)
        m_marks['>'] = m_tc.position();

hjk's avatar
hjk committed
662
    if (m_submode == ChangeSubMode) {
663
664
        if (m_moveType == MoveInclusive)
            moveRight(); // correction
665
666
        if (anchor() >= position())
           m_anchor++;
hjk's avatar
hjk committed
667
        if (!dotCommand.isEmpty())
668
            setDotCommand("c" + dotCommand);
669
        QString text = removeSelectedText();
670
        //qDebug() << "CHANGING TO INSERT MODE" << text;
hjk's avatar
hjk committed
671
        m_registers[m_register] = text;
672
        enterInsertMode();
hjk's avatar
hjk committed
673
674
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
675
        if (m_moveType == MoveInclusive)
676
            moveRight(); // correction
677
678
        if (anchor() >= position())
           m_anchor++;
hjk's avatar
hjk committed
679
        if (!dotCommand.isEmpty())
680
            setDotCommand("d" + dotCommand);
681
        m_registers[m_register] = removeSelectedText();
hjk's avatar
hjk committed
682
        m_submode = NoSubMode;
683
        if (atEndOfLine())
hjk's avatar
hjk committed
684
            moveLeft();
685
686
        else
            setTargetColumn();
687
    } else if (m_submode == YankSubMode) {
hjk's avatar
hjk committed
688
        m_registers[m_register] = selectedText();
689
        setPosition(m_savedYankPosition);
690
        m_submode = NoSubMode;
691
692
    } else if (m_submode == ReplaceSubMode) {
        m_submode = NoSubMode;
693
    } else if (m_submode == IndentSubMode) {
694
        recordJump();
hjk's avatar
hjk committed
695
        indentRegion();
696
        m_submode = NoSubMode;
697
        updateMiniBuffer();
698
    } else if (m_submode == ShiftRightSubMode) {
699
        recordJump();
700
        shiftRegionRight(1);
701
702
703
        m_submode = NoSubMode;
        updateMiniBuffer();
    } else if (m_submode == ShiftLeftSubMode) {
704
        recordJump();
705
        shiftRegionLeft(1);
706
707
        m_submode = NoSubMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
708
    }
709

hjk's avatar
hjk committed
710
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
711
712
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
713
    m_gflag = false;
hjk's avatar
hjk committed
714
715
    m_register = '"';
    m_tc.clearSelection();
716
717

    updateSelection();
hjk's avatar
hjk committed
718
    updateMiniBuffer();
hjk's avatar
hjk committed
719
720
}

721
722
void FakeVimHandler::Private::updateSelection()
{
hjk's avatar
hjk committed
723
    QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
724
725
726
727
    if (m_visualMode != NoVisualMode) {
        QTextEdit::ExtraSelection sel;
        sel.cursor = m_tc;
        sel.format = m_tc.blockCharFormat();
728
729
730
731
#if 0
        sel.format.setFontWeight(QFont::Bold);
        sel.format.setFontUnderline(true);
#else
hjk's avatar
hjk committed
732
733
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
734
#endif
735
736
737
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
738
        if (m_visualMode == VisualCharMode) {
739
            sel.cursor.setPosition(anchorPos, KeepAnchor);
740
741
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
742
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
743
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
744
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
745
746
747
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
            QTextCursor tc = m_tc;
            tc.setPosition(anchorPos);
            tc.movePosition(StartOfLine, MoveAnchor);
            QTextBlock anchorBlock = tc.block();
            QTextBlock cursorBlock = m_tc.block();
            int anchorColumn = anchorPos - anchorBlock.position();
            int cursorColumn = cursorPos - cursorBlock.position();
            int startColumn = qMin(anchorColumn, cursorColumn);
            int endColumn = qMax(anchorColumn, cursorColumn);
            int endPos = cursorBlock.position();
            while (tc.position() <= endPos) {
                if (startColumn < tc.block().length() - 1) {
                    int last = qMin(tc.block().length() - 1, endColumn);
                    int len = last - startColumn + 1;
                    sel.cursor = tc;
                    sel.cursor.movePosition(Right, MoveAnchor, startColumn);
                    sel.cursor.movePosition(Right, KeepAnchor, len);
                    selections.append(sel);
                }
                tc.movePosition(Down, MoveAnchor, 1);
            }
769
770
        }
    }
771
    //qDebug() << "SELECTION: " << selections;
772
    emit q->selectionChanged(selections);
773
774
}

hjk's avatar
hjk committed
775
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
776
{
hjk's avatar
hjk committed
777
    QString msg;
778
779
    if (m_passing) {
        msg = "-- PASSING --  ";
780
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
781
        msg = m_currentMessage;
hjk's avatar
hjk committed
782
783
784
785
786
787
788
789
    } else if (m_mode == CommandMode && m_visualMode != NoVisualMode) {
        if (m_visualMode == VisualCharMode) {
            msg = "-- VISUAL --";
        } else if (m_visualMode == VisualLineMode) {
            msg = "-- VISUAL LINE --";
        } else if (m_visualMode == VisualBlockMode) {
            msg = "-- VISUAL BLOCK --";
        }
790
    } else if (m_mode == InsertMode) {
hjk's avatar
hjk committed
791
792
793
794
        if (m_submode == ReplaceSubMode)
            msg = "-- REPLACE --";
        else
            msg = "-- INSERT --";
hjk's avatar
hjk committed
795
    } else {
796
        if (m_mode == SearchForwardMode)
797
            msg += '/';
798
        else if (m_mode == SearchBackwardMode)
799
            msg += '?';
800
        else if (m_mode == ExMode)
801
            msg += ':';
hjk's avatar
hjk committed
802
        foreach (QChar c, m_commandBuffer) {
803
804
805
806
807
808
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
809
        }
hjk's avatar
hjk committed
810
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
811
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
812
    }
813

hjk's avatar
hjk committed
814
815
816
817
818
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
819
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
820
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
821
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
822
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
823
824
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
825
    } else {
hjk's avatar
hjk committed
826
        status += "All";
hjk's avatar
hjk committed
827
    }
hjk's avatar
hjk committed
828
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
829
830
}

hjk's avatar
hjk committed
831
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
832
{
833
834
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
835
    updateMiniBuffer();
hjk's avatar
hjk committed
836
837
}

hjk's avatar
hjk committed
838
839
840
841
842
843
844
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

845
846
void FakeVimHandler::Private::notImplementedYet()
{
hjk's avatar
hjk committed
847
    qDebug() << "Not implemented in FakeVim";
848
    showRedMessage(tr("Not implemented in FakeVim"));
849
850
851
    updateMiniBuffer();
}

852
EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
853
    const QString &text)
hjk's avatar
hjk committed
854
{
855
    EventResult handled = EventHandled;
856

hjk's avatar
hjk committed
857
858
859
860
    if (m_submode == WindowSubMode) {
        emit q->windowCommandRequested(key);
        m_submode = NoSubMode;
    } else if (m_submode == RegisterSubMode) {
hjk's avatar
hjk committed
861
862
        m_register = key;
        m_submode = NoSubMode;
863
864
865
866
    } else if (m_submode == ChangeSubMode && key == 'c') { // tested
        moveDown(count() - 1);
        moveToEndOfLine();
        moveLeft();
hjk's avatar
hjk committed
867
        setAnchor();
868
869
870
        moveToStartOfLine();
        setTargetColumn();
        moveUp(count() - 1);
871
        m_moveType = MoveLineWise;
872
873
874
875
        m_lastInsertion.clear();
        setDotCommand("%1cc", count());
        finishMovement();
    } else if (m_submode == DeleteSubMode && key == 'd') { // tested
hjk's avatar
hjk committed
876
        moveToStartOfLine();
877
        setTargetColumn(); 
hjk's avatar
hjk committed
878
        setAnchor();
hjk's avatar
hjk committed
879
        moveDown(count());
880
        m_moveType = MoveLineWise;
881
882
        setDotCommand("%1dd", count());
        finishMovement();
883
    } else if (m_submode == YankSubMode && key == 'y') {
hjk's avatar
hjk committed
884
        moveToStartOfLine();
hjk's avatar
hjk committed
885
        setAnchor();
hjk's avatar
hjk committed
886
        moveDown(count());
hjk's avatar
hjk committed
887
888
        m_moveType = MoveLineWise;
        finishMovement("y");
889
890
891
892
    } else if (m_submode == ShiftLeftSubMode && key == '<') {
        setAnchor();
        moveDown(count() - 1);
        m_moveType = MoveLineWise;
893
        setDotCommand("%1<<", count());
894
895
896
897
898
        finishMovement();
    } else if (m_submode == ShiftRightSubMode && key == '>') {
        setAnchor();
        moveDown(count() - 1);
        m_moveType = MoveLineWise;
899
        setDotCommand("%1>>", count());
900
        finishMovement();
901
    } else if (m_submode == IndentSubMode && key == '=') {
hjk's avatar
hjk committed
902
903
904
        setAnchor();
        moveDown(count() - 1);
        m_moveType = MoveLineWise;
hjk's avatar
hjk committed
905
        setDotCommand("%1==", count());
906
        finishMovement();
hjk's avatar
hjk committed
907
    } else if (m_submode == ZSubMode) {