fakevimhandler.cpp 66 KB
Newer Older
hjk's avatar
hjk committed
1
2
3
4
/***************************************************************************
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/

34
#include "fakevimhandler.h"
hjk's avatar
hjk committed
35

36
37
#include "fakevimconstants.h"

38
39
40
41
42
43
44
// 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.

//#include <indenter.h>

hjk's avatar
hjk committed
45
#include <QtCore/QDebug>
46
#include <QtCore/QFile>
hjk's avatar
hjk committed
47
#include <QtCore/QObject>
48
#include <QtCore/QPointer>
hjk's avatar
hjk committed
49
#include <QtCore/QProcess>
hjk's avatar
hjk committed
50
#include <QtCore/QRegExp>
51
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
52
#include <QtCore/QtAlgorithms>
hjk's avatar
hjk committed
53
54
#include <QtCore/QStack>

hjk's avatar
hjk committed
55
#include <QtGui/QApplication>
hjk's avatar
hjk committed
56
57
58
59
60
61
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
62
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
63
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
64
65
66


using namespace FakeVim::Internal;
67
using namespace FakeVim::Constants;
hjk's avatar
hjk committed
68

69
70
71
72
73
74
75
76
77
#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
hjk's avatar
hjk committed
78
79
80
81
82
83
84


///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
85

86
87
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
88
89
90
91
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

hjk's avatar
hjk committed
92
93
94
95
enum Mode
{
    InsertMode,
    CommandMode,
96
97
98
    ExMode,
    SearchForwardMode,
    SearchBackwardMode,
99
    PassingMode, // lets keyevents to be passed to the main application
hjk's avatar
hjk committed
100
};
hjk's avatar
hjk committed
101

hjk's avatar
hjk committed
102
103
104
105
106
107
enum SubMode
{
    NoSubMode,
    RegisterSubMode,
    ChangeSubMode,
    DeleteSubMode,
hjk's avatar
hjk committed
108
    FilterSubMode,
hjk's avatar
hjk committed
109
    ReplaceSubMode,    // used for R and r
110
    YankSubMode,
111
    IndentSubMode,
112
    ZSubMode,
hjk's avatar
hjk committed
113
};
hjk's avatar
hjk committed
114

hjk's avatar
hjk committed
115
116
enum SubSubMode
{
hjk's avatar
hjk committed
117
118
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
119
    NoSubSubMode,
120
121
    FtSubSubMode,       // used for f, F, t, T
    MarkSubSubMode,     // used for m
122
    BackTickSubSubMode, // used for `
hjk's avatar
hjk committed
123
    TickSubSubMode,     // used for '
hjk's avatar
hjk committed
124
125
};

126
127
128
129
130
131
132
133
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

hjk's avatar
hjk committed
134
135
136
137
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
hjk's avatar
hjk committed
138
    MoveLineWise,
hjk's avatar
hjk committed
139
140
};

141
142
struct EditOperation
{
hjk's avatar
hjk committed
143
144
145
146
147
    EditOperation() : position(-1), itemCount(0) {}
    int position;
    int itemCount; // used to combine several operations
    QString from;
    QString to;
148
149
};

hjk's avatar
hjk committed
150
QDebug &operator<<(QDebug &ts, const EditOperation &op)
hjk's avatar
hjk committed
151
{
hjk's avatar
hjk committed
152
153
    if (op.itemCount > 0) {
        ts << "\n  EDIT BLOCK WITH " << op.itemCount << " ITEMS";
hjk's avatar
hjk committed
154
    } else {
hjk's avatar
hjk committed
155
156
        ts << "\n  EDIT AT " << op.position
           << "\n      FROM " << op.from << "\n      TO " << op.to;
hjk's avatar
hjk committed
157
158
159
160
    }
    return ts;
}

hjk's avatar
hjk committed
161
162
163
164
165
166
int lineCount(const QString &text)
{
    //return text.count(QChar(ParagraphSeparator));
    return text.count(QChar('\n'));
}

hjk's avatar
hjk committed
167
168
169
class FakeVimHandler::Private
{
public:
170
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
171

172
    bool handleEvent(QKeyEvent *ev);
173
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
174

175
176
177
    void setupWidget();
    void restoreWidget();

178
private:
179
    friend class FakeVimHandler;
hjk's avatar
hjk committed
180
181
182
183
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
184
185
186
187
188
    bool handleKey(int key, int unmodified, const QString &text);
    bool handleInsertMode(int key, int unmodified, const QString &text);
    bool handleCommandMode(int key, int unmodified, const QString &text);
    bool handleRegisterMode(int key, int unmodified, const QString &text);
    bool handleMiniBufferModes(int key, int unmodified, const QString &text);
hjk's avatar
hjk committed
189
    void finishMovement(const QString &text = QString());
hjk's avatar
hjk committed
190
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
191

hjk's avatar
hjk committed
192
193
194
    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
195
196
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
197
    bool atEndOfLine() const { return m_tc.atBlockEnd() && m_tc.block().length()>1; }
hjk's avatar
hjk committed
198

hjk's avatar
hjk committed
199
    int lastPositionInDocument() const;
hjk's avatar
hjk committed
200
201
    int positionForLine(int line) const; // 1 based line, 0 based pos
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
hjk's avatar
hjk committed
202

hjk's avatar
hjk committed
203
204
205
206
207
208
    // all zero-based counting
    int cursorLineOnScreen() const;
    int linesOnScreen() const;
    int columnsOnScreen() const;
    int cursorLineInDocument() const;
    int cursorColumnInDocument() const;
hjk's avatar
hjk committed
209
    int linesInDocument() const;
hjk's avatar
hjk committed
210
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
211

212
    // helper functions for indenting
hjk's avatar
hjk committed
213
214
    bool isElectricCharacter(QChar c) const
        { return (c == '{' || c == '}' || c == '#'); }
215
216
217
218
    int indentDist() const;
    void indentRegion(QTextBlock first, QTextBlock last, QChar typedChar=0);
    void indentCurrentLine(QChar typedChar);

hjk's avatar
hjk committed
219
    void moveToFirstNonBlankOnLine();
220
    void moveToDesiredColumn();
hjk's avatar
hjk committed
221
    void moveToNextWord(bool simple);
222
    void moveToMatchingParanthesis();
hjk's avatar
hjk committed
223
    void moveToWordBoundary(bool simple, bool forward);
hjk's avatar
hjk committed
224
225
226
227

    // to reduce line noise
    typedef QTextCursor::MoveOperation MoveOperation;
    typedef QTextCursor::MoveMode MoveMode;
hjk's avatar
hjk committed
228
229
230
231
232
233
234
    void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
    void moveToStartOfLine() { m_tc.movePosition(StartOfLine, MoveAnchor); }
    void moveToEndOfLine() { m_tc.movePosition(EndOfLine, MoveAnchor); }
    void moveUp(int n = 1) { m_tc.movePosition(Up, MoveAnchor, n); }
    void moveDown(int n = 1) { m_tc.movePosition(Down, MoveAnchor, n); }
    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
235
236
237
    void setAnchor() { m_anchor = m_tc.position(); }

    QString selectedText() const;
hjk's avatar
hjk committed
238

hjk's avatar
hjk committed
239
    void handleFfTt(int key);
hjk's avatar
hjk committed
240

hjk's avatar
hjk committed
241
242
    // helper function for handleCommand. return 1 based line index.
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
243
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
244

245
    void enterInsertMode();
246
    void enterCommandMode();
hjk's avatar
hjk committed
247
248
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
249
    void notImplementedYet();
250
251
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
252
    void quit();
253
    QWidget *editor() const;
254

255
256
257
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
258
    bool m_wasReadOnly; // saves read-only state of document
259

hjk's avatar
hjk committed
260
261
262
    FakeVimHandler *q;
    Mode m_mode;
    SubMode m_submode;
hjk's avatar
hjk committed
263
264
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
265
266
    QString m_input;
    QTextCursor m_tc;
hjk's avatar
hjk committed
267
    int m_anchor; 
hjk's avatar
hjk committed
268
269
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
270
271
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
272
    MoveType m_moveType;
hjk's avatar
hjk committed
273
274
275

    bool m_fakeEnd;

276
277
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
278
    int m_gflag;  // whether current command started with 'g'
279

hjk's avatar
hjk committed
280
    QString m_commandBuffer;
281
282
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
283

284
    bool m_lastSearchForward;
hjk's avatar
hjk committed
285
    QString m_lastInsertion;
hjk's avatar
hjk committed
286

287
    // undo handling
hjk's avatar
hjk committed
288
    void recordOperation(const EditOperation &op);
289
    void recordInsert(int position, const QString &data);
hjk's avatar
hjk committed
290
    void recordRemove(int position, const QString &data);
291
    void recordRemove(int position, int length);
hjk's avatar
hjk committed
292
293
294

    void recordRemoveNextChar();
    void recordInsertText(const QString &data);
hjk's avatar
hjk committed
295
    QString recordRemoveSelectedText();
296
    void recordMove();
hjk's avatar
hjk committed
297
298
    void recordBeginGroup();
    void recordEndGroup();
hjk's avatar
hjk committed
299
300
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
hjk's avatar
hjk committed
301

302
303
304
305
    void undo();
    void redo();
    QStack<EditOperation> m_undoStack;
    QStack<EditOperation> m_redoStack;
hjk's avatar
hjk committed
306
    QStack<int> m_undoGroupStack;
307

hjk's avatar
hjk committed
308
309
310
    // extra data for '.'
    QString m_dotCommand;

hjk's avatar
hjk committed
311
    // history for '/'
hjk's avatar
hjk committed
312
313
314
315
    QString lastSearchString() const;
    QStringList m_searchHistory;
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
316
    // history for ':'
hjk's avatar
hjk committed
317
318
    QStringList m_commandHistory;
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
319

hjk's avatar
hjk committed
320
    // visual line mode
321
322
323
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
324

325
326
327
    // marks as lines
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
328
329
    // vi style configuration
    QHash<QString, QString> m_config;
330
331

    // for restoring cursor position
hjk's avatar
hjk committed
332
    int m_savedYankPosition;
333
    int m_desiredColumn;
334
335

    QPointer<QObject> m_extraData;
hjk's avatar
hjk committed
336
337
};

338
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
339
340
{
    q = parent;
hjk's avatar
hjk committed
341

342
343
344
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);

hjk's avatar
hjk committed
345
    m_mode = CommandMode;
hjk's avatar
hjk committed
346
347
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
348
    m_fakeEnd = false;
349
    m_lastSearchForward = true;
hjk's avatar
hjk committed
350
    m_register = '"';
hjk's avatar
hjk committed
351
    m_gflag = false;
352
    m_visualMode = NoVisualMode;
353
    m_desiredColumn = 0;
hjk's avatar
hjk committed
354
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
355
356
    m_anchor = 0;
    m_savedYankPosition = 0;
hjk's avatar
hjk committed
357
358

    m_config[ConfigStartOfLine] = ConfigOn;
359
    m_config[ConfigTabStop]     = "8";
360
    m_config[ConfigSmartTab]    = ConfigOff;
361
    m_config[ConfigShiftWidth]  = "8";
362
    m_config[ConfigExpandTab]   = ConfigOff;
363
    m_config[ConfigAutoIndent]  = ConfigOff;
hjk's avatar
hjk committed
364
365
}

366
bool FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
hjk's avatar
hjk committed
367
{
368
    int key = ev->key();
369
    const int um = key; // keep unmodified key around
370

371
    // FIXME
372
    if (m_mode == PassingMode && key != Qt::Key_Control && key != Qt::Key_Shift) {
373
374
375
376
        if (key == ',') { // use ',,' to leave, too.
            quit();
            return true;
        }
hjk's avatar
hjk committed
377
        m_mode = CommandMode;
378
379
380
        return false;
    }

hjk's avatar
hjk committed
381
382
383
384
385
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
        || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
        return false;

    // Fake "End of line"
386
    m_tc = EDITOR(textCursor());
387
    m_tc.setVisualNavigation(true);
388

hjk's avatar
hjk committed
389
    if (m_fakeEnd)
hjk's avatar
hjk committed
390
        moveRight();
hjk's avatar
hjk committed
391

392
    if ((ev->modifiers() & Qt::ControlModifier) != 0) {
hjk's avatar
hjk committed
393
        key += 256;
394
395
396
397
398
        key += 32; // make it lower case
    } else if (key >= Key_A && key <= Key_Z
        && (ev->modifiers() & Qt::ShiftModifier) == 0) {
        key += 32;
    }
399
    bool handled = handleKey(key, um, ev->text());
hjk's avatar
hjk committed
400
401

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

hjk's avatar
hjk committed
404
    if (m_fakeEnd)
hjk's avatar
hjk committed
405
        moveLeft();
hjk's avatar
hjk committed
406

407
408
    EDITOR(setTextCursor(m_tc));
    EDITOR(ensureCursorVisible());
hjk's avatar
hjk committed
409
    return handled;
hjk's avatar
hjk committed
410
411
}

412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
    if (m_textedit) {
        m_textedit->installEventFilter(q);
        //m_textedit->setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
        m_wasReadOnly = m_textedit->isReadOnly();
    } else if (m_plaintextedit) {
        m_plaintextedit->installEventFilter(q);
        //plaintextedit->setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
        m_wasReadOnly = m_plaintextedit->isReadOnly();
    }
    showBlackMessage("vi emulation mode.");
    updateMiniBuffer();
}

void FakeVimHandler::Private::restoreWidget()
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
    if (m_textedit) {
        m_textedit->removeEventFilter(q);
        m_textedit->setReadOnly(m_wasReadOnly);
    } else if (m_plaintextedit) {
        m_plaintextedit->removeEventFilter(q);
        m_plaintextedit->setReadOnly(m_wasReadOnly);
    }
}

443
bool FakeVimHandler::Private::handleKey(int key, int unmodified, const QString &text)
hjk's avatar
hjk committed
444
{
445
446
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
    //qDebug() << "\nUNDO: " << m_undoStack << "\nREDO: " << m_redoStack;
hjk's avatar
hjk committed
447
    if (m_mode == InsertMode)
448
        return handleInsertMode(key, unmodified, text);
449
    if (m_mode == CommandMode)
450
        return handleCommandMode(key, unmodified, text);
451
    if (m_mode == ExMode || m_mode == SearchForwardMode
452
            || m_mode == SearchBackwardMode)
453
        return handleMiniBufferModes(key, unmodified, text);
454
    return false;
hjk's avatar
hjk committed
455
456
}

hjk's avatar
hjk committed
457
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
458
{
hjk's avatar
hjk committed
459
    if (m_submode == FilterSubMode) {
hjk's avatar
hjk committed
460
461
462
        int beginLine = lineForPosition(anchor());
        int endLine = lineForPosition(position());
        m_tc.setPosition(qMin(anchor(), position()));
hjk's avatar
hjk committed
463
464
465
466
467
468
469
470
        m_mode = ExMode;
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

471
472
473
    if (m_visualMode != NoVisualMode)
        m_marks['>'] = m_tc.position();

hjk's avatar
hjk committed
474
    if (m_submode == ChangeSubMode) {
hjk's avatar
hjk committed
475
476
        if (!dotCommand.isEmpty())
            m_dotCommand = "c" + dotCommand;
hjk's avatar
hjk committed
477
        QString text = recordRemoveSelectedText();
hjk's avatar
hjk committed
478
        qDebug() << "CHANGING TO INSERT MODE" << text;
hjk's avatar
hjk committed
479
        m_registers[m_register] = text;
hjk's avatar
hjk committed
480
481
482
        m_mode = InsertMode;
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
483
484
        if (m_moveType == MoveInclusive)
            moveRight(); // correct 
hjk's avatar
hjk committed
485
486
        if (!dotCommand.isEmpty())
            m_dotCommand = "d" + dotCommand;
hjk's avatar
hjk committed
487
488
        m_registers[m_register] = recordRemoveSelectedText();
        recordEndGroup();
hjk's avatar
hjk committed
489
        m_submode = NoSubMode;
490
        if (atEndOfLine())
hjk's avatar
hjk committed
491
            moveLeft();
492
    } else if (m_submode == YankSubMode) {
hjk's avatar
hjk committed
493
494
        m_registers[m_register] = selectedText();
        m_tc.setPosition(m_savedYankPosition);
495
        m_submode = NoSubMode;
496
497
    } else if (m_submode == ReplaceSubMode) {
        m_submode = NoSubMode;
498
499
500
501
502
    } else if (m_submode == IndentSubMode) {
        QTextDocument *doc = EDITOR(document());
        int start = m_tc.selectionStart();
        int end = m_tc.selectionEnd();
        if (start > end)
hjk's avatar
hjk committed
503
            qSwap(start, end);
504
505
506
507
508
        QTextBlock startBlock = doc->findBlock(start);
        indentRegion(doc->findBlock(start), doc->findBlock(end).next());
        m_tc.setPosition(startBlock.position());
        moveToFirstNonBlankOnLine();
        m_submode = NoSubMode;
hjk's avatar
hjk committed
509
    }
hjk's avatar
hjk committed
510
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
511
512
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
513
    m_gflag = false;
hjk's avatar
hjk committed
514
515
    m_register = '"';
    m_tc.clearSelection();
516
517

    updateSelection();
hjk's avatar
hjk committed
518
    updateMiniBuffer();
519
    m_desiredColumn = leftDist();
hjk's avatar
hjk committed
520
521
}

522
523
524
525
526
527
528
void FakeVimHandler::Private::updateSelection()
{
    QList<QTextEdit::ExtraSelection> selections;
    if (m_visualMode != NoVisualMode) {
        QTextEdit::ExtraSelection sel;
        sel.cursor = m_tc;
        sel.format = m_tc.blockCharFormat();
hjk's avatar
hjk committed
529
530
531
532
        //sel.format.setFontWeight(QFont::Bold);
        //sel.format.setFontUnderline(true);
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
533
534
535
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
536
        if (m_visualMode == VisualCharMode) {
537
            sel.cursor.setPosition(anchorPos, KeepAnchor);
538
539
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
540
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
541
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
542
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
543
544
545
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
            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);
            }
567
568
        }
    }
569
    emit q->selectionChanged(selections);
570
571
}

hjk's avatar
hjk committed
572
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
573
{
hjk's avatar
hjk committed
574
    QString msg;
575
576
577
    if (m_mode == PassingMode) {
        msg = "-- PASSING --";
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
578
579
        msg = m_currentMessage;
        m_currentMessage.clear();
hjk's avatar
hjk committed
580
581
582
583
584
585
586
587
    } 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 --";
        }
588
589
    } else if (m_mode == InsertMode) {
        msg = "-- INSERT --";
hjk's avatar
hjk committed
590
    } else {
591
        if (m_mode == SearchForwardMode)
592
            msg += '/';
593
        else if (m_mode == SearchBackwardMode)
594
            msg += '?';
595
        else if (m_mode == ExMode)
596
            msg += ':';
hjk's avatar
hjk committed
597
        foreach (QChar c, m_commandBuffer) {
598
599
600
601
602
603
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
604
        }
hjk's avatar
hjk committed
605
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
606
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
607
    }
hjk's avatar
hjk committed
608
609
610
611
612
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
613
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
614
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
615
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
616
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
617
618
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
619
    } else {
hjk's avatar
hjk committed
620
        status += "All";
hjk's avatar
hjk committed
621
    }
hjk's avatar
hjk committed
622
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
623
624
}

hjk's avatar
hjk committed
625
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
626
{
627
628
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
629
    updateMiniBuffer();
hjk's avatar
hjk committed
630
631
}

hjk's avatar
hjk committed
632
633
634
635
636
637
638
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

639
640
641
642
643
644
void FakeVimHandler::Private::notImplementedYet()
{
    showRedMessage("Not implemented in FakeVim");
    updateMiniBuffer();
}

645
646
bool FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
647
{
648
    bool handled = true;
649

hjk's avatar
hjk committed
650
651
652
653
    if (m_submode == RegisterSubMode) {
        m_register = key;
        m_submode = NoSubMode;
    } else if (m_submode == ChangeSubMode && key == 'c') {
hjk's avatar
hjk committed
654
655
656
657
        moveToStartOfLine();
        setAnchor();
        moveDown(count());
        moveLeft();
hjk's avatar
hjk committed
658
        m_registers[m_register] = recordRemoveSelectedText();
hjk's avatar
hjk committed
659
660
        m_submode = NoSubMode;
        m_mode = InsertMode;
hjk's avatar
hjk committed
661
        finishMovement("c");
hjk's avatar
hjk committed
662
    } else if (m_submode == DeleteSubMode && key == 'd') {
hjk's avatar
hjk committed
663
        moveToStartOfLine();
hjk's avatar
hjk committed
664
        setAnchor();
hjk's avatar
hjk committed
665
666
        moveDown(count());
        m_registers[m_register] = recordRemoveSelectedText();
hjk's avatar
hjk committed
667
        finishMovement("d");
668
    } else if (m_submode == YankSubMode && key == 'y') {
hjk's avatar
hjk committed
669
        moveToStartOfLine();
hjk's avatar
hjk committed
670
        setAnchor();
hjk's avatar
hjk committed
671
        moveDown(count());
hjk's avatar
hjk committed
672
673
        m_moveType = MoveLineWise;
        finishMovement("y");
674
675
    } else if (m_submode == IndentSubMode && key == '=') {
        indentRegion(m_tc.block(), m_tc.block().next());
676
        finishMovement();
hjk's avatar
hjk committed
677
678
    } else if (m_submode == ZSubMode) {
        if (key == Key_Return) {
hjk's avatar
hjk committed
679
            // cursor line to top of window, cursor on first non-blank
hjk's avatar
hjk committed
680
            scrollToLineInDocument(cursorLineInDocument());
hjk's avatar
hjk committed
681
682
            moveToFirstNonBlankOnLine();
            finishMovement();
hjk's avatar
hjk committed
683
        } else {
hjk's avatar
hjk committed
684
            qDebug() << "IGNORED Z_MODE " << key << text;
hjk's avatar
hjk committed
685
686
        }
        m_submode = NoSubMode;
hjk's avatar
hjk committed
687
688
689
    } else if (m_subsubmode == FtSubSubMode) {
        handleFfTt(key);
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
690
        finishMovement(QString(QChar(m_subsubdata)) + QChar(key));
hjk's avatar
hjk committed
691
    } else if (m_submode == ReplaceSubMode) {
hjk's avatar
hjk committed
692
693
694
695
696
697
698
699
700
        if (count() < rightDist() && text.size() == 1
                && (text.at(0).isPrint() || text.at(0).isSpace())) {
            recordBeginGroup();
            setAnchor();
            moveRight(count());
            recordRemoveSelectedText();
            recordInsertText(QString(count(), text.at(0)));
            recordEndGroup();
            m_moveType = MoveExclusive;
hjk's avatar
hjk committed
701
            m_submode = NoSubMode;
hjk's avatar
hjk committed
702
703
704
            m_dotCommand = QString("%1r%2").arg(count()).arg(text);
            finishMovement();
        } else {
hjk's avatar
hjk committed
705
            m_submode = NoSubMode;
hjk's avatar
hjk committed
706
        }
707
708
709
710
711
712
    } else if (m_subsubmode == MarkSubSubMode) {
        m_marks[key] = m_tc.position();
        m_subsubmode = NoSubSubMode;
    } else if (m_subsubmode == BackTickSubSubMode
            || m_subsubmode == TickSubSubMode) {
        if (m_marks.contains(key)) {
hjk's avatar
hjk committed
713
            m_tc.setPosition(m_marks[key]);
714
715
716
717
            if (m_subsubmode == TickSubSubMode)
                moveToFirstNonBlankOnLine();
            finishMovement();
        } else {
hjk's avatar
hjk committed
718
            showRedMessage(tr("E20: Mark '%1' not set").arg(text));
719
720
        }
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
721
    } else if (key >= '0' && key <= '9') {
hjk's avatar
hjk committed
722
        if (key == '0' && m_mvcount.isEmpty()) {
hjk's avatar
hjk committed
723
            moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
724
725
            finishMovement();
        } else {
hjk's avatar
hjk committed
726
            m_mvcount.append(QChar(key));
hjk's avatar
hjk committed
727
        }
728
    } else if (key == ':') {
hjk's avatar
hjk committed
729
        m_mode = ExMode;
hjk's avatar
hjk committed
730
        m_commandBuffer.clear();
731
        if (m_visualMode != NoVisualMode)
732
733
734
735
736
737
738
739
740
            m_commandBuffer = "'<,'>";
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
    } else if (key == '/' || key == '?') {
        m_mode = (key == '/') ? SearchForwardMode : SearchBackwardMode;
        m_commandBuffer.clear();
        m_searchHistory.append(QString());
        m_searchHistoryIndex = m_searchHistory.size() - 1;
hjk's avatar
hjk committed
741
        updateMiniBuffer();
742
743
    } else if (key == '`') {
        m_subsubmode = BackTickSubSubMode;
hjk's avatar
hjk committed
744
745
746
747
748
749
750
751
    } else if (key == '#' || key == '*') {
        // FIXME: That's not proper vim behaviour
        m_tc.select(QTextCursor::WordUnderCursor);
        QString needle = "\\<" + m_tc.selection().toPlainText() + "\\>";
        m_searchHistory.append(needle);
        m_lastSearchForward = (key == '*');
        updateMiniBuffer();
        search(needle, m_lastSearchForward);
752
753
    } else if (key == '\'') {
        m_subsubmode = TickSubSubMode;
hjk's avatar
hjk committed
754
    } else if (key == '|') {
hjk's avatar
hjk committed
755
756
757
        setAnchor();
        moveToStartOfLine();
        moveRight(qMin(count(), rightDist()) - 1);
hjk's avatar
hjk committed
758
        finishMovement();
hjk's avatar
hjk committed
759
760
761
762
763
764
765
766
    } else if (key == '!' && m_visualMode == NoVisualMode) {
        m_submode = FilterSubMode;
    } else if (key == '!' && m_visualMode == VisualLineMode) {
        m_mode = ExMode;
        m_commandBuffer = "'<,'>!";
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
hjk's avatar
hjk committed
767
768
    } else if (key == '"') {
        m_submode = RegisterSubMode;
769
    } else if (unmodified == Key_Return) {
hjk's avatar
hjk committed
770
771
        moveToStartOfLine();
        moveDown();
772
        moveToFirstNonBlankOnLine();
773
        finishMovement();
hjk's avatar
hjk committed
774
    } else if (key == Key_Home) {
hjk's avatar
hjk committed
775
        moveToStartOfLine();
hjk's avatar
hjk committed
776
777
        finishMovement();
    } else if (key == '$' || key == Key_End) {
778
        int submode = m_submode;
hjk's avatar
hjk committed
779
        moveToEndOfLine();
hjk's avatar
hjk committed
780
        finishMovement();
781
782
        if (submode == NoSubMode)
            m_desiredColumn = -1;
783
784
785
786
    } else if (key == ',') {
        // FIXME: use some other mechanism
        m_mode = PassingMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
787
788
789
790
    } else if (key == '.') {
        qDebug() << "REPEATING" << m_dotCommand;
        for (int i = count(); --i >= 0; )
            foreach (QChar c, m_dotCommand)
791
                handleKey(c.unicode(), c.unicode(), QString(c));
792
793
    } else if (key == '=') {
        m_submode = IndentSubMode;
794
    } else if (key == '%') {
795
        moveToMatchingParanthesis();
796
        finishMovement();
hjk's avatar
hjk committed
797
    } else if (key == 'a') {
hjk's avatar
hjk committed
798
        m_mode = InsertMode;
hjk's avatar
hjk committed
799
        recordBeginGroup();
hjk's avatar
hjk committed
800
        m_lastInsertion.clear();
801
802
        if (!atEndOfLine())
            moveRight();
803
        updateMiniBuffer();
hjk's avatar
hjk committed
804
    } else if (key == 'A') {
hjk's avatar
hjk committed
805
        m_mode = InsertMode;
hjk's avatar
hjk committed
806
807
        moveToEndOfLine();
        recordBeginGroup();
hjk's avatar
hjk committed
808
        m_lastInsertion.clear();
hjk's avatar
hjk committed
809
    } else if (key == 'b') {
810
        m_moveType = MoveExclusive;
hjk's avatar
hjk committed
811
        moveToWordBoundary(false, false);
hjk's avatar
hjk committed
812
813
        finishMovement();
    } else if (key == 'B') {
814
        m_moveType = MoveExclusive;
hjk's avatar
hjk committed
815
        moveToWordBoundary(true, false);
hjk's avatar
hjk committed
816
        finishMovement();
hjk's avatar
hjk committed
817
    } else if (key == 'c') {
hjk's avatar
hjk committed
818
819
        setAnchor();
        recordBeginGroup();
hjk's avatar
hjk committed
820
821
        m_submode = ChangeSubMode;
    } else if (key == 'C') {
hjk's avatar
hjk committed
822
823
824
825
826
        setAnchor();
        recordBeginGroup();
        moveToEndOfLine();
        m_registers[m_register] = recordRemoveSelectedText();
        m_mode = InsertMode;
hjk's avatar
hjk committed
827
        finishMovement();
828
    } else if (key == 'd' && m_visualMode == NoVisualMode) {
829
        if (atEndOfLine())
hjk's avatar
hjk committed
830
831
832
            moveLeft();
        setAnchor();
        recordBeginGroup();
hjk's avatar
hjk committed
833
834
835
        m_opcount = m_mvcount;
        m_mvcount.clear();
        m_submode = DeleteSubMode;
836
    } else if (key == 'd') {
hjk's avatar
hjk committed
837
        setAnchor();
838
839
840
        leaveVisualMode();
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
hjk's avatar
hjk committed
841
        selectRange(beginLine, endLine);
hjk's avatar
hjk committed
842
        recordRemoveSelectedText();
hjk's avatar
hjk committed
843
    } else if (key == 'D') {
hjk's avatar
hjk committed
844
845
        setAnchor();
        recordBeginGroup();
hjk's avatar
hjk committed
846
        m_submode = DeleteSubMode;
hjk's avatar
hjk committed
847
848
        moveDown(qMax(count() - 1, 0));
        moveRight(rightDist());
hjk's avatar
hjk committed
849
        finishMovement();
hjk's avatar
hjk committed
850
    } else if (key == 'e') {
851
        m_moveType = MoveInclusive;
hjk's avatar
hjk committed
852
        moveToWordBoundary(false, true);
hjk's avatar
hjk committed
853
854
        finishMovement();
    } else if (key == 'E') {
855
        m_moveType = MoveInclusive;
hjk's avatar
hjk committed
856
        moveToWordBoundary(true, true);
hjk's avatar
hjk committed
857
        finishMovement();
hjk's avatar
hjk committed
858
859
860
    } else if (key == 'f' || key == 'F') {
        m_subsubmode = FtSubSubMode;
        m_subsubdata = key;
hjk's avatar
hjk committed
861
862
    } else if (key == 'g') {
        m_gflag = true;
hjk's avatar
hjk committed
863
864
    } else if (key == 'G') {
        int n = m_mvcount.isEmpty() ? linesInDocument() : count();
Martin Aumueller's avatar
Martin Aumueller committed
865
        m_tc.setPosition(positionForLine(n), KeepAnchor);
hjk's avatar
hjk committed
866
        if (m_config[ConfigStartOfLine] == ConfigOn)
hjk's avatar
hjk committed
867
868
            moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
869
870
    } else if (key == 'h' || key == Key_Left
            || key == Key_Backspace || key == control('h')) {
hjk's avatar
hjk committed
871
872
873
        int n = qMin(count(), leftDist());
        if (m_fakeEnd && m_tc.block().length() > 1)
            ++n;
hjk's avatar
hjk committed
874
        moveLeft(n);
hjk's avatar
hjk committed
875
        finishMovement();
hjk's avatar
hjk committed
876
    } else if (key == 'H') {
877
        m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
hjk's avatar
hjk committed
878
        moveDown(qMax(count() - 1, 0));
hjk's avatar
hjk committed
879
880
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
881
    } else if (key == 'i') {
882
        enterInsertMode();
883
        updateMiniBuffer();
884
        if (atEndOfLine())
hjk's avatar
hjk committed
885
            moveLeft();
hjk's avatar
hjk committed
886
    } else if (key == 'I') {
hjk's avatar
hjk committed
887
        setAnchor();
888
        enterInsertMode();
hjk's avatar
hjk committed
889
        if (m_gflag)