fakevimhandler.cpp 61.9 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>
hjk's avatar
hjk committed
48
#include <QtCore/QProcess>
hjk's avatar
hjk committed
49
#include <QtCore/QRegExp>
50
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
51
#include <QtCore/QtAlgorithms>
hjk's avatar
hjk committed
52
53
#include <QtCore/QStack>

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


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

68
69
70
71
72
73
74
75
76
#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
77
78
79
80
81
82
83


///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
84

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

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

using namespace Qt;

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

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

hjk's avatar
hjk committed
114
115
116
enum SubSubMode
{
    NoSubSubMode,
117
118
    FtSubSubMode,       // used for f, F, t, T
    MarkSubSubMode,     // used for m
119
    BackTickSubSubMode, // used for `
120
    TickSubSubMode      // used for '
hjk's avatar
hjk committed
121
122
};

123
124
125
126
127
128
129
130
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

131
132
struct EditOperation
{
hjk's avatar
hjk committed
133
    EditOperation() : m_position(-1), m_itemCount(0) {}
134
    int m_position;
hjk's avatar
hjk committed
135
136
137
    int m_itemCount; // used to combine several operations
    QString m_from;
    QString m_to;
138
139
};

hjk's avatar
hjk committed
140
141
142
143
144
145
146
147
148
149
150
QDebug &operator<<(QDebug &ts, const EditOperation &op)
{
    if (op.m_itemCount > 0) {
        ts << "EDIT BLOCK WITH" << op.m_itemCount << "ITEMS";
    } else {
        ts << "EDIT AT " << op.m_position
           << " FROM " << op.m_from << " TO " << op.m_to;
    }
    return ts;
}

hjk's avatar
hjk committed
151
152
153
154
155
class FakeVimHandler::Private
{
public:
    Private(FakeVimHandler *parent);

156
    bool handleEvent(QKeyEvent *ev);
157
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
158

159
private:
160
    friend class FakeVimHandler;
hjk's avatar
hjk committed
161
162
163
164
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
165
166
167
168
169
    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
170
    void finishMovement(const QString &text = QString());
hjk's avatar
hjk committed
171
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
172

hjk's avatar
hjk committed
173
174
175
    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
176
177
178
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
    bool atEol() const { return m_tc.atBlockEnd() && m_tc.block().length()>1; }
hjk's avatar
hjk committed
179

hjk's avatar
hjk committed
180
    int lastPositionInDocument() const;
hjk's avatar
hjk committed
181
182
    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
183

hjk's avatar
hjk committed
184
185
186
187
188
189
    // all zero-based counting
    int cursorLineOnScreen() const;
    int linesOnScreen() const;
    int columnsOnScreen() const;
    int cursorLineInDocument() const;
    int cursorColumnInDocument() const;
hjk's avatar
hjk committed
190
    int linesInDocument() const;
hjk's avatar
hjk committed
191
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
192

193
    // helper functions for indenting
hjk's avatar
hjk committed
194
195
    bool isElectricCharacter(QChar c) const
        { return (c == '{' || c == '}' || c == '#'); }
196
197
198
199
    int indentDist() const;
    void indentRegion(QTextBlock first, QTextBlock last, QChar typedChar=0);
    void indentCurrentLine(QChar typedChar);

hjk's avatar
hjk committed
200
    void moveToFirstNonBlankOnLine();
201
    void moveToDesiredColumn();
hjk's avatar
hjk committed
202
    void moveToNextWord(bool simple);
203
    void moveToMatchingParanthesis();
hjk's avatar
hjk committed
204
    void moveToWordBoundary(bool simple, bool forward);
hjk's avatar
hjk committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223

    // to reduce line noise
    typedef QTextCursor::MoveOperation MoveOperation;
    typedef QTextCursor::MoveMode MoveMode;
    void moveToEndOfDocument(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::End, m, n); }
    void moveToStartOfLine(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::StartOfLine, m, n); }
    void moveToEndOfLine(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::EndOfLine, m, n); }
    void moveUp(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::Up, m, n); }
    void moveDown(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::Down, m, n); }
    void moveRight(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::Right, m, n); }
    void moveLeft(MoveMode m, int n = 1)
        { m_tc.movePosition(QTextCursor::Left, m, n); }

hjk's avatar
hjk committed
224
    void handleFfTt(int key);
hjk's avatar
hjk committed
225

hjk's avatar
hjk committed
226
227
    // helper function for handleCommand. return 1 based line index.
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
228
    QTextCursor selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
229

230
    void setWidget(QWidget *ob);
231
    void enterInsertMode();
232
    void enterCommandMode();
hjk's avatar
hjk committed
233
234
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
235
    void notImplementedYet();
236
237
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
238
    void quit();
239
    QWidget *editor() const;
240

241
242
243
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
244
    bool m_wasReadOnly; // saves read-only state of document
245

hjk's avatar
hjk committed
246
247
248
    FakeVimHandler *q;
    Mode m_mode;
    SubMode m_submode;
hjk's avatar
hjk committed
249
250
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
251
252
253
254
    QString m_input;
    QTextCursor m_tc;
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
255
256
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
257
258
259

    bool m_fakeEnd;

260
261
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
262
    int m_gflag;  // whether current command started with 'g'
263

hjk's avatar
hjk committed
264
    QString m_commandBuffer;
265
266
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
267

268
    bool m_lastSearchForward;
hjk's avatar
hjk committed
269
    QString m_lastInsertion;
hjk's avatar
hjk committed
270

271
    // undo handling
hjk's avatar
hjk committed
272
    void recordOperation(const EditOperation &op);
273
    void recordInsert(int position, const QString &data);
hjk's avatar
hjk committed
274
    void recordRemove(int position, const QString &data);
275
    void recordRemove(int position, int length);
276
    void recordMove(int position, int nestedCount);
hjk's avatar
hjk committed
277
278
279
280
281
282
283

    void recordRemoveNextChar();
    void recordInsertText(const QString &data);
    void recordRemoveSelectedText();
    void recordBeginGroup();
    void recordEndGroup();

284
285
286
287
    void undo();
    void redo();
    QStack<EditOperation> m_undoStack;
    QStack<EditOperation> m_redoStack;
hjk's avatar
hjk committed
288
    QStack<int> m_undoGroupStack;
289

hjk's avatar
hjk committed
290
291
292
293
    // extra data for '.'
    QString m_dotCount;
    QString m_dotCommand;

hjk's avatar
hjk committed
294
    // history for '/'
hjk's avatar
hjk committed
295
296
297
298
    QString lastSearchString() const;
    QStringList m_searchHistory;
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
299
    // history for ':'
hjk's avatar
hjk committed
300
301
    QStringList m_commandHistory;
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
302

hjk's avatar
hjk committed
303
    // visual line mode
304
305
306
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
307

308
309
310
    // marks as lines
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
311
312
    // vi style configuration
    QHash<QString, QString> m_config;
313
314
315

    // for restoring cursor position
    int m_savedPosition;
316
    int m_desiredColumn;
hjk's avatar
hjk committed
317
318
319
320
321
};

FakeVimHandler::Private::Private(FakeVimHandler *parent)
{
    q = parent;
hjk's avatar
hjk committed
322

hjk's avatar
hjk committed
323
    m_mode = CommandMode;
hjk's avatar
hjk committed
324
325
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
326
    m_fakeEnd = false;
327
    m_lastSearchForward = true;
hjk's avatar
hjk committed
328
    m_register = '"';
hjk's avatar
hjk committed
329
    m_gflag = false;
330
331
    m_textedit = 0;
    m_plaintextedit = 0;
332
    m_visualMode = NoVisualMode;
333
    m_desiredColumn = 0;
hjk's avatar
hjk committed
334
335

    m_config[ConfigStartOfLine] = ConfigOn;
336
    m_config[ConfigTabStop]     = "8";
337
    m_config[ConfigSmartTab]    = ConfigOff;
338
    m_config[ConfigShiftWidth]  = "8";
339
    m_config[ConfigExpandTab]   = ConfigOff;
340
    m_config[ConfigAutoIndent]  = ConfigOff;
hjk's avatar
hjk committed
341
342
}

343
bool FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
hjk's avatar
hjk committed
344
{
345
    int key = ev->key();
346
    const int um = key; // keep unmodified key around
347

348
    // FIXME
349
    if (m_mode == PassingMode && key != Qt::Key_Control && key != Qt::Key_Shift) {
350
351
352
353
        if (key == ',') { // use ',,' to leave, too.
            quit();
            return true;
        }
354
355
356
        return false;
    }

hjk's avatar
hjk committed
357
358
359
360
361
    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"
362
    m_tc = EDITOR(textCursor());
363
    m_tc.setVisualNavigation(true);
364

hjk's avatar
hjk committed
365
    if (m_fakeEnd)
hjk's avatar
hjk committed
366
        moveRight(MoveAnchor);
hjk's avatar
hjk committed
367

368
    if ((ev->modifiers() & Qt::ControlModifier) != 0) {
hjk's avatar
hjk committed
369
        key += 256;
370
371
372
373
374
        key += 32; // make it lower case
    } else if (key >= Key_A && key <= Key_Z
        && (ev->modifiers() & Qt::ShiftModifier) == 0) {
        key += 32;
    }
375
    bool handled = handleKey(key, um, ev->text());
hjk's avatar
hjk committed
376
377

    // We fake vi-style end-of-line behaviour
hjk's avatar
hjk committed
378
    m_fakeEnd = (atEol() && m_mode == CommandMode);
hjk's avatar
hjk committed
379

hjk's avatar
hjk committed
380
    if (m_fakeEnd)
hjk's avatar
hjk committed
381
        moveLeft(MoveAnchor);
hjk's avatar
hjk committed
382

383
384
    EDITOR(setTextCursor(m_tc));
    EDITOR(ensureCursorVisible());
hjk's avatar
hjk committed
385
    return handled;
hjk's avatar
hjk committed
386
387
}

388
bool FakeVimHandler::Private::handleKey(int key, int unmodified, const QString &text)
hjk's avatar
hjk committed
389
{
390
391
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
    //qDebug() << "\nUNDO: " << m_undoStack << "\nREDO: " << m_redoStack;
392
    m_savedPosition = m_tc.position();
hjk's avatar
hjk committed
393
    if (m_mode == InsertMode)
394
        return handleInsertMode(key, unmodified, text);
395
    if (m_mode == CommandMode)
396
        return handleCommandMode(key, unmodified, text);
397
    if (m_mode == ExMode || m_mode == SearchForwardMode
398
            || m_mode == SearchBackwardMode)
399
        return handleMiniBufferModes(key, unmodified, text);
400
    return false;
hjk's avatar
hjk committed
401
402
}

hjk's avatar
hjk committed
403
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
404
{
hjk's avatar
hjk committed
405
406
407
    if (m_submode == FilterSubMode) {
        int beginLine = lineForPosition(m_tc.anchor());
        int endLine = lineForPosition(m_tc.position());
hjk's avatar
hjk committed
408
        m_tc.setPosition(qMin(m_tc.anchor(), m_tc.position()));
hjk's avatar
hjk committed
409
410
411
412
413
414
415
416
        m_mode = ExMode;
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

417
418
419
    if (m_visualMode != NoVisualMode)
        m_marks['>'] = m_tc.position();

hjk's avatar
hjk committed
420
    if (m_submode == ChangeSubMode) {
hjk's avatar
hjk committed
421
422
        if (!dotCommand.isEmpty())
            m_dotCommand = "c" + dotCommand;
hjk's avatar
hjk committed
423
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
424
        recordRemoveSelectedText();
hjk's avatar
hjk committed
425
426
427
        m_mode = InsertMode;
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
hjk's avatar
hjk committed
428
429
        if (!dotCommand.isEmpty())
            m_dotCommand = "d" + dotCommand;
hjk's avatar
hjk committed
430
        recordRemove(qMin(m_tc.position(), m_tc.anchor()), m_tc.selectedText());
hjk's avatar
hjk committed
431
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
432
        recordRemoveSelectedText();
hjk's avatar
hjk committed
433
434
        m_submode = NoSubMode;
        if (atEol())
hjk's avatar
hjk committed
435
            moveLeft(MoveAnchor);
436
437
438
439
    } else if (m_submode == YankSubMode) {
        m_registers[m_register] = m_tc.selectedText();
        m_tc.setPosition(m_savedPosition);
        m_submode = NoSubMode;
440
441
    } else if (m_submode == ReplaceSubMode) {
        m_submode = NoSubMode;
442
443
444
445
446
    } 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
447
            qSwap(start, end);
448
449
450
451
452
        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
453
    }
hjk's avatar
hjk committed
454
455
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
456
    m_gflag = false;
hjk's avatar
hjk committed
457
458
    m_register = '"';
    m_tc.clearSelection();
459
460

    updateSelection();
hjk's avatar
hjk committed
461
    updateMiniBuffer();
462
    m_desiredColumn = leftDist();
hjk's avatar
hjk committed
463
464
}

465
466
467
468
469
470
471
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
472
473
474
475
        //sel.format.setFontWeight(QFont::Bold);
        //sel.format.setFontUnderline(true);
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
476
477
478
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
479
        if (m_visualMode == VisualCharMode) {
480
            sel.cursor.setPosition(anchorPos, KeepAnchor);
481
482
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
483
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
484
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
485
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
486
487
488
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
            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);
            }
510
511
        }
    }
512
    emit q->selectionChanged(editor(), selections);
513
514
}

hjk's avatar
hjk committed
515
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
516
{
hjk's avatar
hjk committed
517
    QString msg;
518
519
520
    if (m_mode == PassingMode) {
        msg = "-- PASSING --";
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
521
522
        msg = m_currentMessage;
        m_currentMessage.clear();
hjk's avatar
hjk committed
523
524
525
526
527
528
529
530
    } 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 --";
        }
531
532
    } else if (m_mode == InsertMode) {
        msg = "-- INSERT --";
hjk's avatar
hjk committed
533
    } else {
534
        if (m_mode == SearchForwardMode)
535
            msg += '/';
536
        else if (m_mode == SearchBackwardMode)
537
            msg += '?';
538
        else if (m_mode == ExMode)
539
            msg += ':';
hjk's avatar
hjk committed
540
        foreach (QChar c, m_commandBuffer) {
541
542
543
544
545
546
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
547
        }
hjk's avatar
hjk committed
548
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
549
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
550
    }
hjk's avatar
hjk committed
551
552
553
554
555
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
556
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
557
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
558
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
559
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
560
561
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
562
    } else {
hjk's avatar
hjk committed
563
        status += "All";
hjk's avatar
hjk committed
564
    }
hjk's avatar
hjk committed
565
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
566
567
}

hjk's avatar
hjk committed
568
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
569
{
570
571
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
572
    updateMiniBuffer();
hjk's avatar
hjk committed
573
574
}

hjk's avatar
hjk committed
575
576
577
578
579
580
581
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

582
583
584
585
586
587
void FakeVimHandler::Private::notImplementedYet()
{
    showRedMessage("Not implemented in FakeVim");
    updateMiniBuffer();
}

588
589
bool FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
590
{
591
    bool handled = true;
592

hjk's avatar
hjk committed
593
594
595
596
    if (m_submode == RegisterSubMode) {
        m_register = key;
        m_submode = NoSubMode;
    } else if (m_submode == ChangeSubMode && key == 'c') {
hjk's avatar
hjk committed
597
598
        moveToStartOfLine(MoveAnchor);
        moveDown(KeepAnchor, count());
hjk's avatar
hjk committed
599
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
600
        finishMovement("c");
hjk's avatar
hjk committed
601
    } else if (m_submode == DeleteSubMode && key == 'd') {
hjk's avatar
hjk committed
602
603
        moveToStartOfLine(MoveAnchor);
        moveDown(KeepAnchor, count());
hjk's avatar
hjk committed
604
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
605
        finishMovement("d");
606
    } else if (m_submode == YankSubMode && key == 'y') {
hjk's avatar
hjk committed
607
608
        moveToStartOfLine(MoveAnchor);
        moveDown(KeepAnchor, count());
609
610
        m_registers[m_register] = m_tc.selectedText();
        finishMovement();
611
612
    } else if (m_submode == ReplaceSubMode) {
        if (atEol())
hjk's avatar
hjk committed
613
            moveLeft(KeepAnchor);
614
615
616
        else
            m_tc.deleteChar();
        m_tc.insertText(text);
617
618
    } else if (m_submode == IndentSubMode && key == '=') {
        indentRegion(m_tc.block(), m_tc.block().next());
619
        finishMovement();
hjk's avatar
hjk committed
620
621
    } else if (m_submode == ZSubMode) {
        if (key == Key_Return) {
hjk's avatar
hjk committed
622
            // cursor line to top of window, cursor on first non-blank
hjk's avatar
hjk committed
623
            scrollToLineInDocument(cursorLineInDocument());
hjk's avatar
hjk committed
624
625
            moveToFirstNonBlankOnLine();
            finishMovement();
hjk's avatar
hjk committed
626
        } else {
hjk's avatar
hjk committed
627
            qDebug() << "IGNORED Z_MODE " << key << text;
hjk's avatar
hjk committed
628
629
        }
        m_submode = NoSubMode;
hjk's avatar
hjk committed
630
631
632
    } else if (m_subsubmode == FtSubSubMode) {
        handleFfTt(key);
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
633
        finishMovement(QString(QChar(m_subsubdata)) + QChar(key));
634
635
636
637
638
639
    } 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
640
            m_tc.setPosition(m_marks[key], MoveAnchor);
641
642
643
644
            if (m_subsubmode == TickSubSubMode)
                moveToFirstNonBlankOnLine();
            finishMovement();
        } else {
hjk's avatar
hjk committed
645
            showRedMessage(tr("E20: Mark '%1' not set").arg(text));
646
647
        }
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
648
    } else if (key >= '0' && key <= '9') {
hjk's avatar
hjk committed
649
        if (key == '0' && m_mvcount.isEmpty()) {
hjk's avatar
hjk committed
650
            moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
651
652
            finishMovement();
        } else {
hjk's avatar
hjk committed
653
            m_mvcount.append(QChar(key));
hjk's avatar
hjk committed
654
        }
655
    } else if (key == ':') {
hjk's avatar
hjk committed
656
        m_mode = ExMode;
hjk's avatar
hjk committed
657
        m_commandBuffer.clear();
658
        if (m_visualMode != NoVisualMode)
659
660
661
662
663
664
665
666
667
            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
668
        updateMiniBuffer();
669
670
    } else if (key == '`') {
        m_subsubmode = BackTickSubSubMode;
hjk's avatar
hjk committed
671
672
673
674
675
676
677
678
    } 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);
679
680
    } else if (key == '\'') {
        m_subsubmode = TickSubSubMode;
hjk's avatar
hjk committed
681
    } else if (key == '|') {
hjk's avatar
hjk committed
682
683
        moveToStartOfLine(KeepAnchor);
        moveRight(KeepAnchor, qMin(count(), rightDist()) - 1);
hjk's avatar
hjk committed
684
        finishMovement();
hjk's avatar
hjk committed
685
686
687
688
689
690
691
692
    } 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
693
694
    } else if (key == '"') {
        m_submode = RegisterSubMode;
695
    } else if (unmodified == Key_Return) {
hjk's avatar
hjk committed
696
697
        moveToStartOfLine(MoveAnchor);
        moveDown(MoveAnchor);
698
        moveToFirstNonBlankOnLine();
699
        finishMovement();
hjk's avatar
hjk committed
700
701
702
703
    } else if (key == Key_Home) {
        m_tc.movePosition(StartOfLine, KeepAnchor);
        finishMovement();
    } else if (key == '$' || key == Key_End) {
704
        int submode = m_submode;
hjk's avatar
hjk committed
705
        moveToEndOfLine(KeepAnchor);
hjk's avatar
hjk committed
706
        finishMovement();
707
708
        if (submode == NoSubMode)
            m_desiredColumn = -1;
709
710
711
712
    } else if (key == ',') {
        // FIXME: use some other mechanism
        m_mode = PassingMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
713
714
715
716
    } else if (key == '.') {
        qDebug() << "REPEATING" << m_dotCommand;
        for (int i = count(); --i >= 0; )
            foreach (QChar c, m_dotCommand)
717
                handleKey(c.unicode(), c.unicode(), QString(c));
718
719
    } else if (key == '=') {
        m_submode = IndentSubMode;
720
    } else if (key == '%') {
721
        moveToMatchingParanthesis();
722
        finishMovement();
hjk's avatar
hjk committed
723
    } else if (key == 'a') {
hjk's avatar
hjk committed
724
        m_mode = InsertMode;
hjk's avatar
hjk committed
725
726
        m_lastInsertion.clear();
        m_tc.movePosition(Right, MoveAnchor, 1);
727
        updateMiniBuffer();
hjk's avatar
hjk committed
728
    } else if (key == 'A') {
hjk's avatar
hjk committed
729
        m_mode = InsertMode;
hjk's avatar
hjk committed
730
        moveToEndOfLine(MoveAnchor);
hjk's avatar
hjk committed
731
        m_lastInsertion.clear();
hjk's avatar
hjk committed
732
    } else if (key == 'b') {
hjk's avatar
hjk committed
733
        moveToWordBoundary(false, false);
hjk's avatar
hjk committed
734
735
        finishMovement();
    } else if (key == 'B') {
hjk's avatar
hjk committed
736
        moveToWordBoundary(true, false);
hjk's avatar
hjk committed
737
        finishMovement();
hjk's avatar
hjk committed
738
739
740
741
    } else if (key == 'c') {
        m_submode = ChangeSubMode;
    } else if (key == 'C') {
        m_submode = ChangeSubMode;
hjk's avatar
hjk committed
742
        moveToEndOfLine(KeepAnchor);
hjk's avatar
hjk committed
743
        finishMovement();
744
    } else if (key == 'd' && m_visualMode == NoVisualMode) {
hjk's avatar
hjk committed
745
        if (atEol())
hjk's avatar
hjk committed
746
            moveLeft(MoveAnchor);
hjk's avatar
hjk committed
747
748
749
        m_opcount = m_mvcount;
        m_mvcount.clear();
        m_submode = DeleteSubMode;
750
751
752
753
754
    } else if (key == 'd') {
        leaveVisualMode();
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
        m_tc = selectRange(beginLine, endLine);
hjk's avatar
hjk committed
755
        recordRemoveSelectedText();
hjk's avatar
hjk committed
756
757
    } else if (key == 'D') {
        m_submode = DeleteSubMode;
hjk's avatar
hjk committed
758
759
        moveDown(KeepAnchor, qMax(count() - 1, 0));
        moveRight(KeepAnchor, rightDist());
hjk's avatar
hjk committed
760
        finishMovement();
hjk's avatar
hjk committed
761
    } else if (key == 'e') {
hjk's avatar
hjk committed
762
        moveToWordBoundary(false, true);
hjk's avatar
hjk committed
763
764
        finishMovement();
    } else if (key == 'E') {
hjk's avatar
hjk committed
765
        moveToWordBoundary(true, true);
hjk's avatar
hjk committed
766
        finishMovement();
hjk's avatar
hjk committed
767
768
769
    } else if (key == 'f' || key == 'F') {
        m_subsubmode = FtSubSubMode;
        m_subsubdata = key;
hjk's avatar
hjk committed
770
771
    } else if (key == 'g') {
        m_gflag = true;
hjk's avatar
hjk committed
772
773
    } else if (key == 'G') {
        int n = m_mvcount.isEmpty() ? linesInDocument() : count();
Martin Aumueller's avatar
Martin Aumueller committed
774
        m_tc.setPosition(positionForLine(n), KeepAnchor);
hjk's avatar
hjk committed
775
        if (m_config[ConfigStartOfLine] == ConfigOn)
hjk's avatar
hjk committed
776
777
            moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
778
779
780
781
    } else if (key == 'h' || key == Key_Left) {
        int n = qMin(count(), leftDist());
        if (m_fakeEnd && m_tc.block().length() > 1)
            ++n;
hjk's avatar
hjk committed
782
        moveLeft(KeepAnchor, n);
hjk's avatar
hjk committed
783
        finishMovement();
hjk's avatar
hjk committed
784
    } else if (key == 'H') {
785
        m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
hjk's avatar
hjk committed
786
        moveDown(KeepAnchor, qMax(count() - 1, 0));
hjk's avatar
hjk committed
787
788
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
789
    } else if (key == 'i') {
790
        enterInsertMode();
791
        updateMiniBuffer();
792
        if (atEol())
hjk's avatar
hjk committed
793
            moveLeft(MoveAnchor);
hjk's avatar
hjk committed
794
    } else if (key == 'I') {
795
        enterInsertMode();
hjk's avatar
hjk committed
796
        if (m_gflag)
hjk's avatar
hjk committed
797
            moveToStartOfLine(KeepAnchor);
hjk's avatar
hjk committed
798
799
        else
            moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
800
    } else if (key == 'j' || key == Key_Down) {
801
        int savedColumn = m_desiredColumn;
hjk's avatar
hjk committed
802
803
        if (m_submode == NoSubMode || m_submode == ZSubMode
                || m_submode == RegisterSubMode) {
hjk's avatar
hjk committed
804
            moveDown(KeepAnchor, count());
805
            moveToDesiredColumn();
806
        } else {
hjk's avatar
hjk committed
807
808
            moveToStartOfLine(MoveAnchor);
            moveDown(KeepAnchor, count() + 1);
809
        }
hjk's avatar
hjk committed
810
        finishMovement();
811
        m_desiredColumn = savedColumn;
hjk's avatar
hjk committed
812
    } else if (key == 'J') {
hjk's avatar
hjk committed
813
        recordBeginGroup();
hjk's avatar
hjk committed
814
815
        if (m_submode == NoSubMode) {
            for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
hjk's avatar
hjk committed
816
                moveToEndOfLine(MoveAnchor);
hjk's avatar
hjk committed
817
                recordRemoveNextChar();
hjk's avatar
hjk committed
818
                if (!m_gflag)
hjk's avatar
hjk committed
819
                    recordInsertText(" ");
hjk's avatar
hjk committed
820
            }
hjk's avatar
hjk committed
821
            if (!m_gflag)
hjk's avatar
hjk committed
822
                moveLeft(MoveAnchor, 1);
hjk's avatar
hjk committed
823
        }
hjk's avatar
hjk committed
824
        recordEndGroup();
hjk's avatar
hjk committed
825
    } else if (key == 'k' || key == Key_Up) {
826
        int savedColumn = m_desiredColumn;
827
        if (m_submode == NoSubMode || m_submode == ZSubMode || m_submode == RegisterSubMode) {
hjk's avatar
hjk committed
828
            moveUp(KeepAnchor, count());
829
            moveToDesiredColumn();
830
        } else {
hjk's avatar
hjk committed
831
832
833
            moveToStartOfLine(MoveAnchor);
            moveDown(MoveAnchor);
            moveUp(KeepAnchor, count() + 1);
834
        }
hjk's avatar
hjk committed
835
        finishMovement();
836
        m_desiredColumn = savedColumn;
hjk's avatar
hjk committed
837
    } else if (key == 'l' || key == Key_Right) {
hjk's avatar
hjk committed
838
        moveRight(KeepAnchor, qMin(count(), rightDist()));
hjk's avatar
hjk committed
839
        finishMovement();
hjk's avatar
hjk committed
840
    } else if (key == 'L') {
hjk's avatar
hjk committed
841
        m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
hjk's avatar
hjk committed
842
        moveUp(KeepAnchor, qMax(count(), 1));
hjk's avatar
hjk committed
843
844
        moveToFirstNonBlankOnLine();
        finishMovement();
845
846
    } else if (key == 'm') {
        m_subsubmode = MarkSubSubMode;
hjk's avatar
hjk committed
847
    } else if (key == 'M') {
hjk's avatar
hjk committed
848
        m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
hjk's avatar
hjk committed
849
850
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
851
    } else if (key == 'n') {
hjk's avatar
hjk committed
852
        search(lastSearchString(), m_lastSearchForward);
hjk's avatar
hjk committed
853
    } else if (key == 'N') {
hjk's avatar
hjk committed
854
        search(lastSearchString(), !m_lastSearchForward);
855
    } else if (key == 'o' || key == 'O') {
856
        enterInsertMode();
857
858
        moveToFirstNonBlankOnLine();
        int numSpaces = leftDist();
hjk's avatar
hjk committed
859
        moveUp(MoveAnchor, 1);
860
861
        if (key == 'o')
            m_tc.movePosition(Down, MoveAnchor, 1);
hjk's avatar
hjk committed
862
        moveToEndOfLine(MoveAnchor);
hjk's avatar
hjk committed
863
        m_tc.insertText("\n");
hjk's avatar
hjk committed
864
        moveToStartOfLine(MoveAnchor);
865
866
867
        if (m_config[ConfigAutoIndent] == ConfigOn)
            m_tc.insertText(QString(indentDist(), ' '));
        else
868
            m_tc.insertText(QString(numSpaces, ' '));
hjk's avatar
hjk committed