fakevimhandler.cpp 50.2 KB
Newer Older
hjk's avatar
hjk committed
1
2
3
4
5
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
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** 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"

hjk's avatar
hjk committed
38
#include <QtCore/QDebug>
39
#include <QtCore/QFile>
hjk's avatar
hjk committed
40
#include <QtCore/QObject>
hjk's avatar
hjk committed
41
#include <QtCore/QProcess>
hjk's avatar
hjk committed
42
#include <QtCore/QRegExp>
43
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
44
45
#include <QtCore/QStack>

hjk's avatar
hjk committed
46
#include <QtGui/QApplication>
hjk's avatar
hjk committed
47
48
49
50
51
52
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
53
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
54
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
55
56
57


using namespace FakeVim::Internal;
58
using namespace FakeVim::Constants;
hjk's avatar
hjk committed
59

60
61
62
63
64
65
66
67
68
#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
69
70
71
72
73
74
75


///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
76

77
78
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
79
80
81
82
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

hjk's avatar
hjk committed
83
84
85
86
enum Mode
{
    InsertMode,
    CommandMode,
87
88
89
    ExMode,
    SearchForwardMode,
    SearchBackwardMode,
90
    PassingMode, // lets keyevents to be passed to the main application
hjk's avatar
hjk committed
91
};
hjk's avatar
hjk committed
92

hjk's avatar
hjk committed
93
94
95
96
97
98
enum SubMode
{
    NoSubMode,
    RegisterSubMode,
    ChangeSubMode,
    DeleteSubMode,
hjk's avatar
hjk committed
99
    FilterSubMode,
100
    ZSubMode,
hjk's avatar
hjk committed
101
};
hjk's avatar
hjk committed
102

hjk's avatar
hjk committed
103
104
105
enum SubSubMode
{
    NoSubSubMode,
106
107
    FtSubSubMode,       // used for f, F, t, T
    MarkSubSubMode,     // used for m
108
    BackTickSubSubMode, // used for `
109
    TickSubSubMode      // used for '
hjk's avatar
hjk committed
110
111
};

112
113
114
115
116
117
118
119
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

120
121
struct EditOperation
{
hjk's avatar
hjk committed
122
    EditOperation() : m_position(-1), m_itemCount(0) {}
123
    int m_position;
hjk's avatar
hjk committed
124
125
126
    int m_itemCount; // used to combine several operations
    QString m_from;
    QString m_to;
127
128
};

hjk's avatar
hjk committed
129
130
131
132
133
134
135
136
137
138
139
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
140
141
142
143
144
class FakeVimHandler::Private
{
public:
    Private(FakeVimHandler *parent);

145
    bool handleEvent(QKeyEvent *ev);
146
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
147

148
private:
149
    friend class FakeVimHandler;
hjk's avatar
hjk committed
150
151
152
153
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
154
155
156
157
158
    bool handleKey(int key, const QString &text);
    bool handleInsertMode(int key, const QString &text);
    bool handleCommandMode(int key, const QString &text);
    bool handleRegisterMode(int key, const QString &text);
    bool handleMiniBufferModes(int key, const QString &text);
hjk's avatar
hjk committed
159
    void finishMovement(const QString &text = QString());
hjk's avatar
hjk committed
160
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
161

hjk's avatar
hjk committed
162
163
164
    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
165
166
167
    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
168

hjk's avatar
hjk committed
169
    int lastPositionInDocument() const;
hjk's avatar
hjk committed
170
171
    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
172

hjk's avatar
hjk committed
173
174
175
176
177
178
    // all zero-based counting
    int cursorLineOnScreen() const;
    int linesOnScreen() const;
    int columnsOnScreen() const;
    int cursorLineInDocument() const;
    int cursorColumnInDocument() const;
hjk's avatar
hjk committed
179
    int linesInDocument() const;
hjk's avatar
hjk committed
180
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
181

hjk's avatar
hjk committed
182
    void moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
183
184
185
    void moveToNextWord(bool simple);
    void moveToWordBoundary(bool simple, bool forward);
    void handleFfTt(int key);
hjk's avatar
hjk committed
186

hjk's avatar
hjk committed
187
188
    // helper function for handleCommand. return 1 based line index.
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
189
    QTextCursor selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
190

191
    void setWidget(QWidget *ob);
192
    void enterInsertMode();
193
    void enterCommandMode();
hjk's avatar
hjk committed
194
195
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
196
197
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
198
    void quit();
199
    QWidget *editor() const;
200

201
202
203
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
204
    bool m_wasReadOnly; // saves read-only state of document
205

hjk's avatar
hjk committed
206
207
208
    FakeVimHandler *q;
    Mode m_mode;
    SubMode m_submode;
hjk's avatar
hjk committed
209
210
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
211
212
213
214
    QString m_input;
    QTextCursor m_tc;
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
215
216
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
217
218
219

    bool m_fakeEnd;

220
221
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
222
    int m_gflag;  // whether current command started with 'g'
223

hjk's avatar
hjk committed
224
    QString m_commandBuffer;
225
226
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
227

228
    bool m_lastSearchForward;
hjk's avatar
hjk committed
229
    QString m_lastInsertion;
hjk's avatar
hjk committed
230

231
    // undo handling
hjk's avatar
hjk committed
232
    void recordOperation(const EditOperation &op);
233
    void recordInsert(int position, const QString &data);
hjk's avatar
hjk committed
234
    void recordRemove(int position, const QString &data);
235
    void recordRemove(int position, int length);
236
    void recordMove(int position, int nestedCount);
237
    void removeSelectedText(QTextCursor &tc);
238
239
240
241
242
    void undo();
    void redo();
    QStack<EditOperation> m_undoStack;
    QStack<EditOperation> m_redoStack;

hjk's avatar
hjk committed
243
244
245
246
    // extra data for '.'
    QString m_dotCount;
    QString m_dotCommand;

hjk's avatar
hjk committed
247
    // history for '/'
hjk's avatar
hjk committed
248
249
250
251
    QString lastSearchString() const;
    QStringList m_searchHistory;
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
252
    // history for ':'
hjk's avatar
hjk committed
253
254
    QStringList m_commandHistory;
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
255

hjk's avatar
hjk committed
256
    // visual line mode
257
258
259
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
260

261
262
263
    // marks as lines
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
264
265
    // vi style configuration
    QHash<QString, QString> m_config;
hjk's avatar
hjk committed
266
267
268
269
270
};

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

hjk's avatar
hjk committed
272
    m_mode = CommandMode;
hjk's avatar
hjk committed
273
274
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
275
    m_fakeEnd = false;
276
    m_lastSearchForward = true;
hjk's avatar
hjk committed
277
    m_register = '"';
hjk's avatar
hjk committed
278
    m_gflag = false;
279
280
    m_textedit = 0;
    m_plaintextedit = 0;
281
    m_visualMode = NoVisualMode;
hjk's avatar
hjk committed
282
283

    m_config[ConfigStartOfLine] = ConfigOn;
284
    m_config[ConfigTabStop]     = "8";
285
    m_config[ConfigSmartTab]    = ConfigOff;
286
    m_config[ConfigShiftWidth]  = "8";
287
    m_config[ConfigExpandTab]   = ConfigOff;
hjk's avatar
hjk committed
288
289
}

290
bool FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
hjk's avatar
hjk committed
291
{
292
293
    int key = ev->key();

294
    // FIXME
295
    if (m_mode == PassingMode && key != Qt::Key_Control && key != Qt::Key_Shift) {
296
297
298
299
        if (key == ',') { // use ',,' to leave, too.
            quit();
            return true;
        }
300
301
302
        return false;
    }

hjk's avatar
hjk committed
303
304
305
306
307
    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"
308
309
    m_tc = EDITOR(textCursor());

hjk's avatar
hjk committed
310
    if (m_fakeEnd)
hjk's avatar
hjk committed
311
312
313
        m_tc.movePosition(Right, MoveAnchor, 1);

    if (key >= Key_A && key <= Key_Z
314
        && (ev->modifiers() & Qt::ShiftModifier) == 0)
hjk's avatar
hjk committed
315
        key += 32;
316
    if ((ev->modifiers() & Qt::ControlModifier) != 0)
hjk's avatar
hjk committed
317
        key += 256;
318
    bool handled = handleKey(key, ev->text());
hjk's avatar
hjk committed
319
320

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

hjk's avatar
hjk committed
323
    if (m_fakeEnd)
hjk's avatar
hjk committed
324
325
        m_tc.movePosition(Left, MoveAnchor, 1);

326
327
    EDITOR(setTextCursor(m_tc));
    EDITOR(ensureCursorVisible());
hjk's avatar
hjk committed
328
    return handled;
hjk's avatar
hjk committed
329
330
}

331
bool FakeVimHandler::Private::handleKey(int key, const QString &text)
hjk's avatar
hjk committed
332
{
333
334
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
    //qDebug() << "\nUNDO: " << m_undoStack << "\nREDO: " << m_redoStack;
hjk's avatar
hjk committed
335
    if (m_mode == InsertMode)
336
337
338
339
        return handleInsertMode(key, text);
    if (m_mode == CommandMode)
        return handleCommandMode(key, text);
    if (m_mode == ExMode || m_mode == SearchForwardMode
340
            || m_mode == SearchBackwardMode)
341
342
        return handleMiniBufferModes(key, text);
    return false;
hjk's avatar
hjk committed
343
344
}

hjk's avatar
hjk committed
345
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
346
{
hjk's avatar
hjk committed
347
348
349
350
351
352
353
354
355
356
357
358
    if (m_submode == FilterSubMode) {
        int beginLine = lineForPosition(m_tc.anchor());
        int endLine = lineForPosition(m_tc.position());
        m_tc.setPosition(qMin(m_tc.anchor(), m_tc.position()), MoveAnchor);
        m_mode = ExMode;
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

hjk's avatar
hjk committed
359
    if (m_submode == ChangeSubMode) {
hjk's avatar
hjk committed
360
361
        if (!dotCommand.isEmpty())
            m_dotCommand = "c" + dotCommand;
hjk's avatar
hjk committed
362
        m_registers[m_register] = m_tc.selectedText();
363
        removeSelectedText(m_tc);
hjk's avatar
hjk committed
364
365
366
        m_mode = InsertMode;
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
hjk's avatar
hjk committed
367
368
        if (!dotCommand.isEmpty())
            m_dotCommand = "d" + dotCommand;
hjk's avatar
hjk committed
369
        recordRemove(qMin(m_tc.position(), m_tc.anchor()), m_tc.selectedText());
hjk's avatar
hjk committed
370
        m_registers[m_register] = m_tc.selectedText();
371
        removeSelectedText(m_tc);
hjk's avatar
hjk committed
372
373
374
375
        m_submode = NoSubMode;
        if (atEol())
            m_tc.movePosition(Left, MoveAnchor, 1);
    }
hjk's avatar
hjk committed
376
377
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
378
    m_gflag = false;
hjk's avatar
hjk committed
379
380
    m_register = '"';
    m_tc.clearSelection();
381
382

    updateSelection();
hjk's avatar
hjk committed
383
    updateMiniBuffer();
hjk's avatar
hjk committed
384
385
}

386
387
388
389
390
391
392
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
393
394
395
396
        //sel.format.setFontWeight(QFont::Bold);
        //sel.format.setFontUnderline(true);
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
397
398
399
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
400
        if (m_visualMode == VisualCharMode) {
401
            sel.cursor.setPosition(anchorPos, KeepAnchor);
402
403
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
404
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
405
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
406
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
407
408
409
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
            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);
            }
431
432
433
434
435
        }
    }
    EDITOR(setExtraSelections(selections));
}

hjk's avatar
hjk committed
436
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
437
{
hjk's avatar
hjk committed
438
    QString msg;
439
440
441
    if (m_mode == PassingMode) {
        msg = "-- PASSING --";
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
442
443
        msg = m_currentMessage;
        m_currentMessage.clear();
hjk's avatar
hjk committed
444
445
446
447
448
449
450
451
    } 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 --";
        }
452
453
    } else if (m_mode == InsertMode) {
        msg = "-- INSERT --";
hjk's avatar
hjk committed
454
    } else {
455
        if (m_mode == SearchForwardMode)
456
            msg += '/';
457
        else if (m_mode == SearchBackwardMode)
458
            msg += '?';
459
        else if (m_mode == ExMode)
460
            msg += ':';
hjk's avatar
hjk committed
461
        foreach (QChar c, m_commandBuffer) {
462
463
464
465
466
467
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
468
        }
hjk's avatar
hjk committed
469
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
470
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
471
    }
hjk's avatar
hjk committed
472
473
474
475
476
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
477
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
478
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
479
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
480
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
481
482
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
483
    } else {
hjk's avatar
hjk committed
484
        status += "All";
hjk's avatar
hjk committed
485
    }
hjk's avatar
hjk committed
486
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
487
488
}

hjk's avatar
hjk committed
489
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
490
{
491
492
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
493
    updateMiniBuffer();
hjk's avatar
hjk committed
494
495
}

hjk's avatar
hjk committed
496
497
498
499
500
501
502
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

503
bool FakeVimHandler::Private::handleCommandMode(int key, const QString &text)
hjk's avatar
hjk committed
504
{
505
506
    Q_UNUSED(text)

hjk's avatar
hjk committed
507
508
509
510
511
512
513
    if (m_submode == RegisterSubMode) {
        m_register = key;
        m_submode = NoSubMode;
    } else if (m_submode == ChangeSubMode && key == 'c') {
        m_tc.movePosition(StartOfLine, MoveAnchor);
        m_tc.movePosition(Down, KeepAnchor, count());
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
514
        finishMovement("c");
hjk's avatar
hjk committed
515
516
517
518
    } else if (m_submode == DeleteSubMode && key == 'd') {
        m_tc.movePosition(StartOfLine, MoveAnchor);
        m_tc.movePosition(Down, KeepAnchor, count());
        m_registers[m_register] = m_tc.selectedText();
hjk's avatar
hjk committed
519
        finishMovement("d");
hjk's avatar
hjk committed
520
521
    } else if (m_submode == ZSubMode) {
        if (key == Key_Return) {
hjk's avatar
hjk committed
522
            // cursor line to top of window, cursor on first non-blank
hjk's avatar
hjk committed
523
            scrollToLineInDocument(cursorLineInDocument());
hjk's avatar
hjk committed
524
525
            moveToFirstNonBlankOnLine();
            finishMovement();
hjk's avatar
hjk committed
526
        } else {
hjk's avatar
hjk committed
527
528
529
            qDebug() << "Ignored z + " << key << text;
        }
        m_submode = NoSubMode;
hjk's avatar
hjk committed
530
531
532
    } else if (m_subsubmode == FtSubSubMode) {
        handleFfTt(key);
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
533
        finishMovement(QString(QChar(m_subsubdata)) + QChar(key));
534
535
536
537
538
539
    } 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
540
            m_tc.setPosition(m_marks[key], MoveAnchor);
541
542
543
544
            if (m_subsubmode == TickSubSubMode)
                moveToFirstNonBlankOnLine();
            finishMovement();
        } else {
hjk's avatar
hjk committed
545
            showRedMessage(tr("E20: Mark '%1' not set").arg(text));
546
547
        }
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
548
    } else if (key >= '0' && key <= '9') {
hjk's avatar
hjk committed
549
        if (key == '0' && m_mvcount.isEmpty()) {
hjk's avatar
hjk committed
550
            moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
551
552
            finishMovement();
        } else {
hjk's avatar
hjk committed
553
            m_mvcount.append(QChar(key));
hjk's avatar
hjk committed
554
        }
555
    } else if (key == ':') {
hjk's avatar
hjk committed
556
        m_mode = ExMode;
hjk's avatar
hjk committed
557
        m_commandBuffer.clear();
558
559
560
        if (m_visualMode != NoVisualMode) {
            m_commandBuffer = "'<,'>";
            leaveVisualMode();
hjk's avatar
hjk committed
561
        }
562
563
564
565
566
567
568
569
        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
570
        updateMiniBuffer();
571
572
573
574
    } else if (key == '`') {
        m_subsubmode = BackTickSubSubMode;
    } else if (key == '\'') {
        m_subsubmode = TickSubSubMode;
hjk's avatar
hjk committed
575
576
577
578
    } else if (key == '|') {
        m_tc.movePosition(StartOfLine, KeepAnchor);
        m_tc.movePosition(Right, KeepAnchor, qMin(count(), rightDist()) - 1);
        finishMovement();
hjk's avatar
hjk committed
579
580
581
582
583
584
585
586
587
    } else if (key == '!' && m_visualMode == NoVisualMode) {
        m_submode = FilterSubMode;
    } else if (key == '!' && m_visualMode == VisualLineMode) {
        m_mode = ExMode;
        m_marks['>'] = m_tc.position();
        m_commandBuffer = "'<,'>!";
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
hjk's avatar
hjk committed
588
589
590
591
592
593
594
595
596
597
598
    } else if (key == '"') {
        m_submode = RegisterSubMode;
    } else if (key == Key_Return) {
        m_tc.movePosition(StartOfLine);
        m_tc.movePosition(Down);
    } else if (key == Key_Home) {
        m_tc.movePosition(StartOfLine, KeepAnchor);
        finishMovement();
    } else if (key == '$' || key == Key_End) {
        m_tc.movePosition(EndOfLine, KeepAnchor);
        finishMovement();
599
600
601
602
    } else if (key == ',') {
        // FIXME: use some other mechanism
        m_mode = PassingMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
603
604
605
606
607
    } else if (key == '.') {
        qDebug() << "REPEATING" << m_dotCommand;
        for (int i = count(); --i >= 0; )
            foreach (QChar c, m_dotCommand)
                handleKey(c.unicode(), QString(c));
hjk's avatar
hjk committed
608
    } else if (key == 'a') {
hjk's avatar
hjk committed
609
        m_mode = InsertMode;
hjk's avatar
hjk committed
610
611
        m_lastInsertion.clear();
        m_tc.movePosition(Right, MoveAnchor, 1);
612
        updateMiniBuffer();
hjk's avatar
hjk committed
613
    } else if (key == 'A') {
hjk's avatar
hjk committed
614
        m_mode = InsertMode;
hjk's avatar
hjk committed
615
        m_tc.movePosition(EndOfLine, MoveAnchor);
hjk's avatar
hjk committed
616
        m_lastInsertion.clear();
hjk's avatar
hjk committed
617
    } else if (key == 'b') {
hjk's avatar
hjk committed
618
        moveToWordBoundary(false, false);
hjk's avatar
hjk committed
619
620
        finishMovement();
    } else if (key == 'B') {
hjk's avatar
hjk committed
621
        moveToWordBoundary(true, false);
hjk's avatar
hjk committed
622
        finishMovement();
hjk's avatar
hjk committed
623
624
625
626
627
628
    } else if (key == 'c') {
        m_submode = ChangeSubMode;
    } else if (key == 'C') {
        m_submode = ChangeSubMode;
        m_tc.movePosition(EndOfLine, KeepAnchor);
        finishMovement();
629
    } else if (key == 'd' && m_visualMode == NoVisualMode) {
hjk's avatar
hjk committed
630
631
632
633
634
        if (atEol())
            m_tc.movePosition(Left, MoveAnchor, 1);
        m_opcount = m_mvcount;
        m_mvcount.clear();
        m_submode = DeleteSubMode;
635
636
637
638
639
    } else if (key == 'd') {
        leaveVisualMode();
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
        m_tc = selectRange(beginLine, endLine);
640
        removeSelectedText(m_tc);
hjk's avatar
hjk committed
641
642
    } else if (key == 'D') {
        m_submode = DeleteSubMode;
hjk's avatar
hjk committed
643
644
        m_tc.movePosition(Down, KeepAnchor, qMax(count() - 1, 0));
        m_tc.movePosition(Right, KeepAnchor, rightDist());
hjk's avatar
hjk committed
645
        finishMovement();
hjk's avatar
hjk committed
646
    } else if (key == 'e') {
hjk's avatar
hjk committed
647
        moveToWordBoundary(false, true);
hjk's avatar
hjk committed
648
649
        finishMovement();
    } else if (key == 'E') {
hjk's avatar
hjk committed
650
        moveToWordBoundary(true, true);
hjk's avatar
hjk committed
651
        finishMovement();
hjk's avatar
hjk committed
652
653
654
    } else if (key == 'f' || key == 'F') {
        m_subsubmode = FtSubSubMode;
        m_subsubdata = key;
hjk's avatar
hjk committed
655
656
    } else if (key == 'g') {
        m_gflag = true;
hjk's avatar
hjk committed
657
658
    } else if (key == 'G') {
        int n = m_mvcount.isEmpty() ? linesInDocument() : count();
hjk's avatar
hjk committed
659
        m_tc.setPosition(positionForLine(n), MoveAnchor);
hjk's avatar
hjk committed
660
661
662
        if (m_config.contains(ConfigStartOfLine))
            moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
663
664
665
666
667
668
    } else if (key == 'h' || key == Key_Left) {
        int n = qMin(count(), leftDist());
        if (m_fakeEnd && m_tc.block().length() > 1)
            ++n;
        m_tc.movePosition(Left, KeepAnchor, n);
        finishMovement();
hjk's avatar
hjk committed
669
    } else if (key == 'H') {
670
        m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
hjk's avatar
hjk committed
671
672
673
        m_tc.movePosition(Down, KeepAnchor, qMax(count() - 1, 0));
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
674
    } else if (key == 'i') {
675
        enterInsertMode();
676
        updateMiniBuffer();
hjk's avatar
hjk committed
677
    } else if (key == 'I') {
678
        enterInsertMode();
hjk's avatar
hjk committed
679
680
681
682
        if (m_gflag)
            m_tc.movePosition(StartOfLine, KeepAnchor);
        else
            moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
683
684
685
    } else if (key == 'j' || key == Key_Down) {
        m_tc.movePosition(Down, KeepAnchor, count());
        finishMovement();
hjk's avatar
hjk committed
686
    } else if (key == 'J') {
hjk's avatar
hjk committed
687
        EditOperation op;
hjk's avatar
hjk committed
688
689
690
691
        if (m_submode == NoSubMode) {
            for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
                m_tc.movePosition(EndOfLine);
                m_tc.deleteChar();
hjk's avatar
hjk committed
692
693
                if (!m_gflag)
                    m_tc.insertText(" ");
hjk's avatar
hjk committed
694
            }
hjk's avatar
hjk committed
695
696
            if (!m_gflag)
                m_tc.movePosition(Left, MoveAnchor, 1);
hjk's avatar
hjk committed
697
        }
hjk's avatar
hjk committed
698
699
700
701
702
703
    } else if (key == 'k' || key == Key_Up) {
        m_tc.movePosition(Up, KeepAnchor, count());
        finishMovement();
    } else if (key == 'l' || key == Key_Right) {
        m_tc.movePosition(Right, KeepAnchor, qMin(count(), rightDist()));
        finishMovement();
hjk's avatar
hjk committed
704
    } else if (key == 'L') {
hjk's avatar
hjk committed
705
        m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
hjk's avatar
hjk committed
706
707
708
        m_tc.movePosition(Up, KeepAnchor, qMax(count(), 1));
        moveToFirstNonBlankOnLine();
        finishMovement();
709
710
    } else if (key == 'm') {
        m_subsubmode = MarkSubSubMode;
hjk's avatar
hjk committed
711
    } else if (key == 'M') {
hjk's avatar
hjk committed
712
        m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
hjk's avatar
hjk committed
713
714
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
715
    } else if (key == 'n') {
hjk's avatar
hjk committed
716
        search(lastSearchString(), m_lastSearchForward);
hjk's avatar
hjk committed
717
    } else if (key == 'N') {
hjk's avatar
hjk committed
718
        search(lastSearchString(), !m_lastSearchForward);
hjk's avatar
hjk committed
719
    } else if (key == 'o') {
720
        enterInsertMode();
hjk's avatar
hjk committed
721
722
        m_tc.movePosition(EndOfLine, MoveAnchor);
        m_tc.insertText("\n");
hjk's avatar
hjk committed
723
    } else if (key == 'O') {
724
        enterInsertMode();
hjk's avatar
hjk committed
725
726
727
        m_tc.movePosition(StartOfLine, MoveAnchor);
        m_tc.movePosition(Left, MoveAnchor, 1);
        m_tc.insertText("\n");
hjk's avatar
hjk committed
728
    } else if (key == 'p' || key == 'P') {
hjk's avatar
hjk committed
729
730
731
732
        QString text = m_registers[m_register];
        int n = text.count(QChar(ParagraphSeparator));
        if (n > 0) {
            m_tc.movePosition(StartOfLine);
hjk's avatar
hjk committed
733
734
            if (key == 'p')
                m_tc.movePosition(Down);
hjk's avatar
hjk committed
735
736
737
            m_tc.insertText(text);
            m_tc.movePosition(Up, MoveAnchor, n);
        } else {
hjk's avatar
hjk committed
738
739
            if (key == 'p')
                m_tc.movePosition(Right);
hjk's avatar
hjk committed
740
741
742
            m_tc.insertText(text);
            m_tc.movePosition(Left);
        }
743
        m_dotCommand = "p";
hjk's avatar
hjk committed
744
    } else if (key == control('r')) {
745
        redo();
hjk's avatar
hjk committed
746
747
748
    } else if (key == 's') {
        m_submode = ChangeSubMode;
        m_tc.movePosition(Right, KeepAnchor, qMin(count(), rightDist()));
hjk's avatar
hjk committed
749
750
751
    } else if (key == 't' || key == 'T') {
        m_subsubmode = FtSubSubMode;
        m_subsubdata = key;
hjk's avatar
hjk committed
752
    } else if (key == 'u') {
753
        undo();
754
755
756
757
    } else if (key == 'U') {
        // FIXME: this is non-vim, but as Ctrl-R is taken globally
        // we have a substitute here
        redo();
758
759
    } else if (key == 'v') {
        enterVisualMode(VisualCharMode);
hjk's avatar
hjk committed
760
    } else if (key == 'V') {
761
762
763
        enterVisualMode(VisualLineMode);
    } else if (key == control('v')) {
        enterVisualMode(VisualBlockMode);
hjk's avatar
hjk committed
764
    } else if (key == 'w') {
hjk's avatar
hjk committed
765
        moveToNextWord(false);
hjk's avatar
hjk committed
766
        finishMovement("w");
hjk's avatar
hjk committed
767
    } else if (key == 'W') {
hjk's avatar
hjk committed
768
        moveToNextWord(true);
hjk's avatar
hjk committed
769
        finishMovement("W");
hjk's avatar
hjk committed
770
771
772
773
774
    } else if (key == 'x') { // = "dl"
        if (atEol())
            m_tc.movePosition(Left, MoveAnchor, 1);
        m_submode = DeleteSubMode;
        m_tc.movePosition(Right, KeepAnchor, qMin(count(), rightDist()));
hjk's avatar
hjk committed
775
        finishMovement("l");
hjk's avatar
hjk committed
776
777
778
779
780
781
    } else if (key == 'X') {
        if (leftDist() > 0) {
            m_tc.movePosition(Left, KeepAnchor, qMin(count(), leftDist()));
            m_tc.deleteChar();
        }
        finishMovement();
hjk's avatar
hjk committed
782
783
    } else if (key == 'z') {
        m_submode = ZSubMode;
hjk's avatar
hjk committed
784
    } else if (key == '~' && !atEol()) {
hjk's avatar
hjk committed
785
786
787
788
789
790
791
792
        m_tc.movePosition(Right, KeepAnchor, qMin(count(), rightDist()));
        QString str = m_tc.selectedText();
        for (int i = str.size(); --i >= 0; ) {
            QChar c = str.at(i);
            str[i] = c.isUpper() ? c.toLower() : c.toUpper();
        }
        m_tc.deleteChar();
        m_tc.insertText(str);
hjk's avatar
hjk committed
793
794
795
796
797
798
    } else if (key == Key_PageDown || key == control('f')) {
        m_tc.movePosition(Down, KeepAnchor, count() * (linesOnScreen() - 2));
        finishMovement();
    } else if (key == Key_PageUp || key == control('b')) {
        m_tc.movePosition(Up, KeepAnchor, count() * (linesOnScreen() - 2));
        finishMovement();
hjk's avatar
hjk committed
799
    } else if (key == Key_Backspace || key == control('h')) {
hjk's avatar
hjk committed
800
        m_tc.deletePreviousChar();
801
802
    } else if (key == Key_Delete) {
        m_tc.deleteChar();
hjk's avatar
hjk committed
803
    } else if (key == Key_Escape) {
804
805
        if (m_visualMode != NoVisualMode)
            leaveVisualMode();
hjk's avatar
hjk committed
806
    } else {
hjk's avatar
hjk committed
807
        qDebug() << "Ignored in command mode: " << key << text;
808
809
810
        return false;
    }
    return true;
hjk's avatar
hjk committed
811
812
}

813
bool FakeVimHandler::Private::handleInsertMode(int key, const QString &text)
hjk's avatar
hjk committed
814
815
{
    if (key == Key_Escape) {
816
817
818
819
        // start with '1', as one instance was already physically inserted
        // while typing
        QString data = m_lastInsertion;
        for (int i = 1; i < count(); ++i) {
hjk's avatar
hjk committed
820
            m_tc.insertText(m_lastInsertion);
821
822
823
            data += m_lastInsertion;
        }
        recordInsert(m_tc.position() - m_lastInsertion.size(), data);
hjk's avatar
hjk committed
824
        m_tc.movePosition(Left, MoveAnchor, qMin(1, leftDist()));
825
        enterCommandMode();
hjk's avatar
hjk committed
826
827
    } else if (key == Key_Left) {
        m_tc.movePosition(Left, MoveAnchor, 1);
hjk's avatar
hjk committed
828
        m_lastInsertion.clear();
hjk's avatar
hjk committed
829
830
    } else if (key == Key_Down) {
        m_tc.movePosition(Down, MoveAnchor, 1);
hjk's avatar
hjk committed
831
        m_lastInsertion.clear();
hjk's avatar
hjk committed
832
833
    } else if (key == Key_Up) {
        m_tc.movePosition(Up, MoveAnchor, 1);
hjk's avatar
hjk committed
834
        m_lastInsertion.clear();
hjk's avatar
hjk committed
835
836
    } else if (key == Key_Right) {
        m_tc.movePosition(Right, MoveAnchor, 1);
hjk's avatar
hjk committed
837
        m_lastInsertion.clear();
hjk's avatar
hjk committed
838
839
    } else if (key == Key_Return) {
        m_tc.insertBlock();
840
        m_lastInsertion += "\n";
hjk's avatar
hjk committed
841
    } else if (key == Key_Backspace || key == control('h')) {
hjk's avatar
hjk committed
842
        m_tc.deletePreviousChar();
hjk's avatar
hjk committed
843
        m_lastInsertion = m_lastInsertion.left(m_lastInsertion.size() - 1);
844
845
    } else if (key == Key_Delete) {
        m_tc.deleteChar();
hjk's avatar
hjk committed
846
        m_lastInsertion.clear();
hjk's avatar
hjk committed
847
848
    } else if (key == Key_PageDown || key == control('f')) {
        m_tc.movePosition(Down, KeepAnchor, count() * (linesOnScreen() - 2));
hjk's avatar
hjk committed
849
        m_lastInsertion.clear();
hjk's avatar
hjk committed
850
851
    } else if (key == Key_PageUp || key == control('b')) {
        m_tc.movePosition(Up, KeepAnchor, count() * (linesOnScreen() - 2));
hjk's avatar
hjk committed
852
        m_lastInsertion.clear();
853
854
855
856
    } else if (key == Key_Tab && m_config[ConfigExpandTab] == ConfigOn) {
        QString str = QString(m_config[ConfigTabStop].toInt(), ' ');
        m_lastInsertion.append(str);
        m_tc.insertText(str);
857
    } else if (!text.isEmpty()) {
hjk's avatar
hjk committed
858
        m_lastInsertion.append(text);
859
        m_tc.insertText(text);
860
861
    } else {
        return false;
862
863
    }
    updateMiniBuffer();
864
    return true;
hjk's avatar
hjk committed
865
866
}

867
bool FakeVimHandler::Private::handleMiniBufferModes(int key, const QString &text)
hjk's avatar
hjk committed
868
{
869
870
    Q_UNUSED(text)

hjk's avatar
hjk committed
871
872
    if (key == Key_Escape) {
        m_commandBuffer.clear();
873
        enterCommandMode();
hjk's avatar
hjk committed
874
        updateMiniBuffer();
hjk's avatar
hjk committed
875
876
    } else if (key == Key_Backspace) {
        if (m_commandBuffer.isEmpty())
877
            enterCommandMode();
hjk's avatar
hjk committed
878
879
        else
            m_commandBuffer.chop(1);
hjk's avatar
hjk committed
880
        updateMiniBuffer();
hjk's avatar
hjk committed
881
882
883
884
885
    } else if (key == Key_Left) {
        // FIXME:
        if (!m_commandBuffer.isEmpty())
            m_commandBuffer.chop(1);
        updateMiniBuffer();
886
    } else if (key == Key_Return && m_mode == ExMode) {
hjk's avatar
hjk committed
887
888
889
        if (!m_commandBuffer.isEmpty()) {
            m_commandHistory.takeLast();
            m_commandHistory.append(m_commandBuffer);
hjk's avatar
hjk committed
890
            handleExCommand(m_commandBuffer);
hjk's avatar
hjk committed
891
        }
892
    } else if (key == Key_Return && isSearchMode()) {
hjk's avatar
hjk committed
893
894
895
        if (!m_commandBuffer.isEmpty()) {
            m_searchHistory.takeLast();
            m_searchHistory.append(m_commandBuffer);
896
            m_lastSearchForward = (m_mode == SearchForwardMode);
hjk's avatar
hjk committed
897
898
            search(lastSearchString(), m_lastSearchForward);
        }
899
        enterCommandMode();
hjk's avatar
hjk committed
900
        updateMiniBuffer();
901
    } else if (key == Key_Up && isSearchMode()) {
902
903
        // FIXME: This and the three cases below are wrong as vim
        // takes only matching entires in the history into account.
904
        if (m_searchHistoryIndex > 0) {
hjk's avatar
hjk committed
905
            --m_searchHistoryIndex;
hjk's avatar
hjk committed
906
            showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
907
        }
908
    } else if (key == Key_Up && m_mode == ExMode) {
909
        if (m_commandHistoryIndex > 0) {
hjk's avatar
hjk committed
910
            --m_commandHistoryIndex;
hjk's avatar
hjk committed
911
            showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
hjk's avatar
hjk committed
912
        }
913
    } else if (key == Key_Down && isSearchMode()) {
914
        if (m_searchHistoryIndex < m_searchHistory.size() - 1) {
hjk's avatar
hjk committed
915
            ++m_searchHistoryIndex;
hjk's avatar
hjk committed
916
            showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
917
        }
918
    } else if (key == Key_Down && m_mode == ExMode) {
919
        if (m_commandHistoryIndex < m_commandHistory.size() - 1) {