fakevimhandler.cpp 72.4 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
// 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.

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

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


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

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


///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
hjk's avatar
hjk committed
83

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

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

using namespace Qt;

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

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

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

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

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

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

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

159
160
161
162
163
164
165
QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
{
    foreach (QTextEdit::ExtraSelection sel, sels) 
        ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position(); 
    return ts;
}
        
hjk's avatar
hjk committed
166
167
168
169
170
171
int lineCount(const QString &text)
{
    //return text.count(QChar(ParagraphSeparator));
    return text.count(QChar('\n'));
}

hjk's avatar
hjk committed
172
173
174
class FakeVimHandler::Private
{
public:
175
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
176

177
    bool handleEvent(QKeyEvent *ev);
178
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
179

180
181
182
    void setupWidget();
    void restoreWidget();

183
private:
184
    friend class FakeVimHandler;
hjk's avatar
hjk committed
185
186
187
188
    static int shift(int key) { return key + 32; }
    static int control(int key) { return key + 256; }

    void init();
189
190
191
192
193
    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
194
    void finishMovement(const QString &text = QString());
hjk's avatar
hjk committed
195
    void search(const QString &needle, bool forward);
hjk's avatar
hjk committed
196

hjk's avatar
hjk committed
197
198
199
    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
200
201
    int leftDist() const { return m_tc.position() - m_tc.block().position(); }
    int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
202
203
    bool atEndOfLine() const
        { return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
hjk's avatar
hjk committed
204

hjk's avatar
hjk committed
205
    int lastPositionInDocument() const;
206
207
    int firstPositionInLine(int line) const; // 1 based line, 0 based pos
    int lastPositionInLine(int line) const; // 1 based line, 0 based pos
hjk's avatar
hjk committed
208
    int lineForPosition(int pos) const;  // 1 based line, 0 based pos
hjk's avatar
hjk committed
209

hjk's avatar
hjk committed
210
211
212
213
214
215
    // all zero-based counting
    int cursorLineOnScreen() const;
    int linesOnScreen() const;
    int columnsOnScreen() const;
    int cursorLineInDocument() const;
    int cursorColumnInDocument() const;
hjk's avatar
hjk committed
216
    int linesInDocument() const;
hjk's avatar
hjk committed
217
    void scrollToLineInDocument(int line);
hjk's avatar
hjk committed
218

219
    // helper functions for indenting
hjk's avatar
hjk committed
220
    bool isElectricCharacter(QChar c) const
221
        { return c == '{' || c == '}' || c == '#'; }
222
223
224
225
    int indentDist() const;
    void indentRegion(QTextBlock first, QTextBlock last, QChar typedChar=0);
    void indentCurrentLine(QChar typedChar);

hjk's avatar
hjk committed
226
    void moveToFirstNonBlankOnLine();
227
    void moveToDesiredColumn();
hjk's avatar
hjk committed
228
    void moveToNextWord(bool simple);
229
    void moveToMatchingParanthesis();
hjk's avatar
hjk committed
230
    void moveToWordBoundary(bool simple, bool forward);
hjk's avatar
hjk committed
231
232
233
234

    // to reduce line noise
    typedef QTextCursor::MoveOperation MoveOperation;
    typedef QTextCursor::MoveMode MoveMode;
hjk's avatar
hjk committed
235
236
237
238
239
240
241
    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
242
243
    void setAnchor() { m_anchor = m_tc.position(); }

hjk's avatar
hjk committed
244
    void handleFfTt(int key);
hjk's avatar
hjk committed
245

hjk's avatar
hjk committed
246
247
    // helper function for handleCommand. return 1 based line index.
    int readLineCode(QString &cmd);
hjk's avatar
hjk committed
248
    void selectRange(int beginLine, int endLine);
hjk's avatar
hjk committed
249

250
    void enterInsertMode();
251
    void enterCommandMode();
252
    void enterExMode();
hjk's avatar
hjk committed
253
254
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
255
    void notImplementedYet();
256
257
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
258
    void quit();
259
    QWidget *editor() const;
260

261
262
263
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
264
    bool m_wasReadOnly; // saves read-only state of document
265

hjk's avatar
hjk committed
266
267
268
    FakeVimHandler *q;
    Mode m_mode;
    SubMode m_submode;
hjk's avatar
hjk committed
269
270
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
271
272
    QString m_input;
    QTextCursor m_tc;
hjk's avatar
hjk committed
273
    int m_anchor; 
hjk's avatar
hjk committed
274
275
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
276
277
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
278
    MoveType m_moveType;
hjk's avatar
hjk committed
279
280
281

    bool m_fakeEnd;

282
283
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
284
    int m_gflag;  // whether current command started with 'g'
285

hjk's avatar
hjk committed
286
    QString m_commandBuffer;
287
288
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
289

290
    bool m_lastSearchForward;
hjk's avatar
hjk committed
291
    QString m_lastInsertion;
hjk's avatar
hjk committed
292

293
    // undo handling
hjk's avatar
hjk committed
294
    void recordOperation(const EditOperation &op);
295
    void recordInsert(int position, const QString &data);
hjk's avatar
hjk committed
296
    void recordRemove(int position, const QString &data);
297
    void recordRemove(int position, int length);
hjk's avatar
hjk committed
298
299
300

    void recordRemoveNextChar();
    void recordInsertText(const QString &data);
hjk's avatar
hjk committed
301
    QString recordRemoveSelectedText();
302
    void recordMove();
hjk's avatar
hjk committed
303
304
    void recordBeginGroup();
    void recordEndGroup();
hjk's avatar
hjk committed
305
306
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
307
308
    QString selectedText() const;

hjk's avatar
hjk committed
309

310
311
312
313
    void undo();
    void redo();
    QStack<EditOperation> m_undoStack;
    QStack<EditOperation> m_redoStack;
hjk's avatar
hjk committed
314
    QStack<int> m_undoGroupStack;
315

hjk's avatar
hjk committed
316
317
318
    // extra data for '.'
    QString m_dotCommand;

hjk's avatar
hjk committed
319
320
321
322
323
    // extra data for ';'
    QString m_semicolonCount;
    int m_semicolonType;  // 'f', 'F', 't', 'T'
    int m_semicolonKey;

hjk's avatar
hjk committed
324
    // history for '/'
hjk's avatar
hjk committed
325
326
327
328
    QString lastSearchString() const;
    QStringList m_searchHistory;
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
329
    // history for ':'
hjk's avatar
hjk committed
330
331
    QStringList m_commandHistory;
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
332

hjk's avatar
hjk committed
333
    // visual line mode
334
335
336
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
337

338
339
340
    // marks as lines
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
341
342
    // vi style configuration
    QHash<QString, QString> m_config;
343
344

    // for restoring cursor position
hjk's avatar
hjk committed
345
    int m_savedYankPosition;
346
    int m_desiredColumn;
347
348

    QPointer<QObject> m_extraData;
349
    int m_cursorWidth;
350
351
352
353

    void recordJump();
    QList<int> m_jumpListUndo;
    QList<int> m_jumpListRedo;
hjk's avatar
hjk committed
354
355
};

356
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
357
358
{
    q = parent;
hjk's avatar
hjk committed
359

360
361
362
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);

hjk's avatar
hjk committed
363
    m_mode = CommandMode;
hjk's avatar
hjk committed
364
365
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
366
    m_fakeEnd = false;
367
    m_lastSearchForward = true;
hjk's avatar
hjk committed
368
    m_register = '"';
hjk's avatar
hjk committed
369
    m_gflag = false;
370
    m_visualMode = NoVisualMode;
371
    m_desiredColumn = 0;
hjk's avatar
hjk committed
372
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
373
374
    m_anchor = 0;
    m_savedYankPosition = 0;
375
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
376
377

    m_config[ConfigStartOfLine] = ConfigOn;
378
    m_config[ConfigTabStop]     = "8";
379
    m_config[ConfigSmartTab]    = ConfigOff;
380
    m_config[ConfigShiftWidth]  = "8";
381
    m_config[ConfigExpandTab]   = ConfigOff;
382
    m_config[ConfigAutoIndent]  = ConfigOff;
hjk's avatar
hjk committed
383
384
}

385
bool FakeVimHandler::Private::handleEvent(QKeyEvent *ev)
hjk's avatar
hjk committed
386
{
387
    int key = ev->key();
388
    const int um = key; // keep unmodified key around
389

390
    // FIXME
391
    if (m_mode == PassingMode && key != Qt::Key_Control && key != Qt::Key_Shift) {
392
393
394
395
        if (key == ',') { // use ',,' to leave, too.
            quit();
            return true;
        }
hjk's avatar
hjk committed
396
        m_mode = CommandMode;
397
        updateMiniBuffer();
398
399
400
        return false;
    }

hjk's avatar
hjk committed
401
402
403
404
405
    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"
406
    m_tc = EDITOR(textCursor());
407
    m_tc.setVisualNavigation(true);
408

hjk's avatar
hjk committed
409
    if (m_fakeEnd)
hjk's avatar
hjk committed
410
        moveRight();
hjk's avatar
hjk committed
411

412
    if ((ev->modifiers() & Qt::ControlModifier) != 0) {
hjk's avatar
hjk committed
413
        key += 256;
414
415
416
417
418
        key += 32; // make it lower case
    } else if (key >= Key_A && key <= Key_Z
        && (ev->modifiers() & Qt::ShiftModifier) == 0) {
        key += 32;
    }
419
    bool handled = handleKey(key, um, ev->text());
hjk's avatar
hjk committed
420
421

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

hjk's avatar
hjk committed
424
    if (m_fakeEnd)
hjk's avatar
hjk committed
425
        moveLeft();
hjk's avatar
hjk committed
426

427
428
    EDITOR(setTextCursor(m_tc));
    EDITOR(ensureCursorVisible());
hjk's avatar
hjk committed
429
    return handled;
hjk's avatar
hjk committed
430
431
}

432
433
434
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
435
436
    EDITOR(installEventFilter(q));
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
437
438
439
440
441
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
442
    m_wasReadOnly = EDITOR(isReadOnly());
443
    //EDITOR(setReadOnly(true)); 
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458

    QTextCursor tc = EDITOR(textCursor());
    if (tc.hasSelection()) {
        int pos = tc.position();
        int anc = tc.anchor();
        m_marks['<'] = anc;
        m_marks['>'] = pos;
        m_anchor = anc;
        m_visualMode = VisualCharMode;
        tc.clearSelection();
        EDITOR(setTextCursor(tc));
        m_tc = tc; // needed in updateSelection
        updateSelection();
    }

459
460
461
462
463
464
465
466
    showBlackMessage("vi emulation mode.");
    updateMiniBuffer();
}

void FakeVimHandler::Private::restoreWidget()
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
467
468
    EDITOR(removeEventFilter(q));
    EDITOR(setReadOnly(m_wasReadOnly));
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
    
    if (m_visualMode == VisualLineMode) {
        m_tc = EDITOR(textCursor());
        int beginLine = lineForPosition(m_marks['<']);
        int endLine = lineForPosition(m_marks['>']);
        m_tc.setPosition(firstPositionInLine(beginLine), MoveAnchor);
        m_tc.setPosition(lastPositionInLine(endLine), KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    } else if (m_visualMode == VisualCharMode) {
        m_tc = EDITOR(textCursor());
        m_tc.setPosition(m_marks['<'], MoveAnchor);
        m_tc.setPosition(m_marks['>'], KeepAnchor);
        EDITOR(setTextCursor(m_tc));
    }

    m_visualMode = NoVisualMode;
    updateSelection();
486
487
}

488
bool FakeVimHandler::Private::handleKey(int key, int unmodified, const QString &text)
hjk's avatar
hjk committed
489
{
490
491
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
    //qDebug() << "\nUNDO: " << m_undoStack << "\nREDO: " << m_redoStack;
hjk's avatar
hjk committed
492
    if (m_mode == InsertMode)
493
        return handleInsertMode(key, unmodified, text);
494
    if (m_mode == CommandMode)
495
        return handleCommandMode(key, unmodified, text);
496
    if (m_mode == ExMode || m_mode == SearchForwardMode
497
            || m_mode == SearchBackwardMode)
498
        return handleMiniBufferModes(key, unmodified, text);
499
    return false;
hjk's avatar
hjk committed
500
501
}

hjk's avatar
hjk committed
502
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
503
{
504
    //qDebug() << "ANCHOR: " << m_anchor;
hjk's avatar
hjk committed
505
    if (m_submode == FilterSubMode) {
hjk's avatar
hjk committed
506
507
508
        int beginLine = lineForPosition(anchor());
        int endLine = lineForPosition(position());
        m_tc.setPosition(qMin(anchor(), position()));
509
        enterExMode();
hjk's avatar
hjk committed
510
511
512
513
514
515
516
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

517
518
519
    if (m_visualMode != NoVisualMode)
        m_marks['>'] = m_tc.position();

hjk's avatar
hjk committed
520
    if (m_submode == ChangeSubMode) {
521
522
        if (m_moveType == MoveInclusive)
            moveRight(); // correction
hjk's avatar
hjk committed
523
524
        if (!dotCommand.isEmpty())
            m_dotCommand = "c" + dotCommand;
hjk's avatar
hjk committed
525
        QString text = recordRemoveSelectedText();
526
        //qDebug() << "CHANGING TO INSERT MODE" << text;
hjk's avatar
hjk committed
527
        m_registers[m_register] = text;
hjk's avatar
hjk committed
528
529
530
        m_mode = InsertMode;
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
531
        if (m_moveType == MoveInclusive)
532
            moveRight(); // correction
hjk's avatar
hjk committed
533
534
        if (!dotCommand.isEmpty())
            m_dotCommand = "d" + dotCommand;
hjk's avatar
hjk committed
535
536
        m_registers[m_register] = recordRemoveSelectedText();
        recordEndGroup();
hjk's avatar
hjk committed
537
        m_submode = NoSubMode;
538
        if (atEndOfLine())
hjk's avatar
hjk committed
539
            moveLeft();
540
    } else if (m_submode == YankSubMode) {
hjk's avatar
hjk committed
541
542
        m_registers[m_register] = selectedText();
        m_tc.setPosition(m_savedYankPosition);
543
        m_submode = NoSubMode;
544
545
    } else if (m_submode == ReplaceSubMode) {
        m_submode = NoSubMode;
546
547
548
549
550
    } 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
551
            qSwap(start, end);
552
553
554
555
556
        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
557
    }
hjk's avatar
hjk committed
558
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
559
560
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
561
    m_gflag = false;
hjk's avatar
hjk committed
562
563
    m_register = '"';
    m_tc.clearSelection();
564
565

    updateSelection();
hjk's avatar
hjk committed
566
    updateMiniBuffer();
567
    m_desiredColumn = leftDist();
hjk's avatar
hjk committed
568
569
}

570
571
572
573
574
575
576
void FakeVimHandler::Private::updateSelection()
{
    QList<QTextEdit::ExtraSelection> selections;
    if (m_visualMode != NoVisualMode) {
        QTextEdit::ExtraSelection sel;
        sel.cursor = m_tc;
        sel.format = m_tc.blockCharFormat();
577
578
579
580
#if 0
        sel.format.setFontWeight(QFont::Bold);
        sel.format.setFontUnderline(true);
#else
hjk's avatar
hjk committed
581
582
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
583
#endif
584
585
586
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
587
        if (m_visualMode == VisualCharMode) {
588
            sel.cursor.setPosition(anchorPos, KeepAnchor);
589
590
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
591
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
592
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
593
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
594
595
596
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
            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);
            }
618
619
        }
    }
620
    //qDebug() << "SELECTION: " << selections;
621
    emit q->selectionChanged(selections);
622
623
}

hjk's avatar
hjk committed
624
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
625
{
hjk's avatar
hjk committed
626
    QString msg;
627
628
629
    if (m_mode == PassingMode) {
        msg = "-- PASSING --";
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
630
631
        msg = m_currentMessage;
        m_currentMessage.clear();
hjk's avatar
hjk committed
632
633
634
635
636
637
638
639
    } 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 --";
        }
640
641
    } else if (m_mode == InsertMode) {
        msg = "-- INSERT --";
hjk's avatar
hjk committed
642
    } else {
643
        if (m_mode == SearchForwardMode)
644
            msg += '/';
645
        else if (m_mode == SearchBackwardMode)
646
            msg += '?';
647
        else if (m_mode == ExMode)
648
            msg += ':';
hjk's avatar
hjk committed
649
        foreach (QChar c, m_commandBuffer) {
650
651
652
653
654
655
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
656
        }
hjk's avatar
hjk committed
657
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
658
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
659
    }
hjk's avatar
hjk committed
660
661
662
663
664
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
665
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
666
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
667
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
668
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
669
670
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
671
    } else {
hjk's avatar
hjk committed
672
        status += "All";
hjk's avatar
hjk committed
673
    }
hjk's avatar
hjk committed
674
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
675
676
}

hjk's avatar
hjk committed
677
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
678
{
679
680
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
681
    updateMiniBuffer();
hjk's avatar
hjk committed
682
683
}

hjk's avatar
hjk committed
684
685
686
687
688
689
690
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

691
692
693
694
695
696
void FakeVimHandler::Private::notImplementedYet()
{
    showRedMessage("Not implemented in FakeVim");
    updateMiniBuffer();
}

697
698
bool FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
699
{
700
    bool handled = true;
701

hjk's avatar
hjk committed
702
703
704
705
    if (m_submode == RegisterSubMode) {
        m_register = key;
        m_submode = NoSubMode;
    } else if (m_submode == ChangeSubMode && key == 'c') {
hjk's avatar
hjk committed
706
707
708
        moveToStartOfLine();
        setAnchor();
        moveDown(count());
709
        m_moveType = MoveLineWise;
hjk's avatar
hjk committed
710
        finishMovement("c");
hjk's avatar
hjk committed
711
    } else if (m_submode == DeleteSubMode && key == 'd') {
hjk's avatar
hjk committed
712
        moveToStartOfLine();
hjk's avatar
hjk committed
713
        setAnchor();
hjk's avatar
hjk committed
714
        moveDown(count());
715
        m_moveType = MoveLineWise;
hjk's avatar
hjk committed
716
        finishMovement("d");
717
    } else if (m_submode == YankSubMode && key == 'y') {
hjk's avatar
hjk committed
718
        moveToStartOfLine();
hjk's avatar
hjk committed
719
        setAnchor();
hjk's avatar
hjk committed
720
        moveDown(count());
hjk's avatar
hjk committed
721
722
        m_moveType = MoveLineWise;
        finishMovement("y");
723
724
    } else if (m_submode == IndentSubMode && key == '=') {
        indentRegion(m_tc.block(), m_tc.block().next());
725
        finishMovement();
hjk's avatar
hjk committed
726
    } else if (m_submode == ZSubMode) {
hjk's avatar
hjk committed
727
728
729
        //qDebug() << "Z_MODE " << cursorLineInDocument() << linesOnScreen();
        if (key == Key_Return || key == 't') { // cursor line to top of window
            if (!m_mvcount.isEmpty())
730
                m_tc.setPosition(firstPositionInLine(count()));
hjk's avatar
hjk committed
731
            scrollToLineInDocument(cursorLineInDocument());
hjk's avatar
hjk committed
732
733
            if (key == Key_Return)
                moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
734
            finishMovement();
hjk's avatar
hjk committed
735
736
        } else if (key == '.' || key == 'z') { // cursor line to center of window
            if (!m_mvcount.isEmpty())
737
                m_tc.setPosition(firstPositionInLine(count()));
hjk's avatar
hjk committed
738
            scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
hjk's avatar
hjk committed
739
740
            if (key == '.')
                moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
741
            finishMovement();
hjk's avatar
hjk committed
742
743
        } else if (key == '-' || key == 'b') { // cursor line to bottom of window
            if (!m_mvcount.isEmpty())
744
                m_tc.setPosition(firstPositionInLine(count()));
hjk's avatar
hjk committed
745
746
747
            scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() - 1);
            if (key == '-')
                moveToFirstNonBlankOnLine();
hjk's avatar
hjk committed
748
            finishMovement();
hjk's avatar
hjk committed
749
        } else {
hjk's avatar
hjk committed
750
            qDebug() << "IGNORED Z_MODE " << key << text;
hjk's avatar
hjk committed
751
752
        }
        m_submode = NoSubMode;
hjk's avatar
hjk committed
753
    } else if (m_subsubmode == FtSubSubMode) {
hjk's avatar
hjk committed
754
755
        m_semicolonType = m_subsubdata;
        m_semicolonKey = key;
hjk's avatar
hjk committed
756
757
        handleFfTt(key);
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
758
        finishMovement();
hjk's avatar
hjk committed
759
    } else if (m_submode == ReplaceSubMode) {
hjk's avatar
hjk committed
760
761
762
763
764
765
766
767
768
        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
769
            m_submode = NoSubMode;
hjk's avatar
hjk committed
770
771
772
            m_dotCommand = QString("%1r%2").arg(count()).arg(text);
            finishMovement();
        } else {
hjk's avatar
hjk committed
773
            m_submode = NoSubMode;
hjk's avatar
hjk committed
774
        }
775
776
777
778
779
780
    } 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
781
            m_tc.setPosition(m_marks[key]);
782
783
784
785
            if (m_subsubmode == TickSubSubMode)
                moveToFirstNonBlankOnLine();
            finishMovement();
        } else {
hjk's avatar
hjk committed
786
            showRedMessage(tr("E20: Mark '%1' not set").arg(text));
787
788
        }
        m_subsubmode = NoSubSubMode;
hjk's avatar
hjk committed
789
    } else if (key >= '0' && key <= '9') {
hjk's avatar
hjk committed
790
        if (key == '0' && m_mvcount.isEmpty()) {
791
            moveToStartOfLine();
hjk's avatar
hjk committed
792
793
            finishMovement();
        } else {
hjk's avatar
hjk committed
794
            m_mvcount.append(QChar(key));
hjk's avatar
hjk committed
795
        }
796
797
798
    } else if (key == '^') {
        moveToFirstNonBlankOnLine();
        finishMovement();
hjk's avatar
hjk committed
799
800
801
802
803
804
805
806
807
808
809
810
811
812
    } else if (0 && key == ',') {
        // FIXME: fakevim uses ',' by itself, so it is incompatible
        m_subsubmode = FtSubSubMode;
        // HACK: toggle 'f' <-> 'F', 't' <-> 'T'
        m_subsubdata = m_semicolonType ^ 32;
        handleFfTt(m_semicolonKey);
        m_subsubmode = NoSubSubMode;
        finishMovement();
    } else if (key == ';') {
        m_subsubmode = FtSubSubMode;
        m_subsubdata = m_semicolonType;
        handleFfTt(m_semicolonKey);
        m_subsubmode = NoSubSubMode;
        finishMovement();
813
    } else if (key == ':') {
814
        enterExMode();
hjk's avatar
hjk committed
815
        m_commandBuffer.clear();
816
        if (m_visualMode != NoVisualMode)
817
818
819
820
821
            m_commandBuffer = "'<,'>";
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
    } else if (key == '/' || key == '?') {
822
        enterExMode(); // to get the cursor disabled
823
824
825
826
        m_mode = (key == '/') ? SearchForwardMode : SearchBackwardMode;
        m_commandBuffer.clear();
        m_searchHistory.append(QString());
        m_searchHistoryIndex = m_searchHistory.size() - 1;
hjk's avatar
hjk committed
827
        updateMiniBuffer();
828
829
    } else if (key == '`') {
        m_subsubmode = BackTickSubSubMode;
hjk's avatar
hjk committed
830
831
832
833
834
835
836
837
    } 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);
838
        recordJump();
839
840
    } else if (key == '\'') {
        m_subsubmode = TickSubSubMode;
hjk's avatar
hjk committed
841
    } else if (key == '|') {
hjk's avatar
hjk committed
842
843
844
        setAnchor();
        moveToStartOfLine();
        moveRight(qMin(count(), rightDist()) - 1);
hjk's avatar
hjk committed
845
        finishMovement();
hjk's avatar
hjk committed
846
847
848
    } else if (key == '!' && m_visualMode == NoVisualMode) {
        m_submode = FilterSubMode;
    } else if (key == '!' && m_visualMode == VisualLineMode) {
849
        enterExMode();
hjk's avatar
hjk committed
850
851
852
853
        m_commandBuffer = "'<,'>!";
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
hjk's avatar
hjk committed
854
855
    } else if (key == '"') {
        m_submode = RegisterSubMode;
856
    } else if (unmodified == Key_Return) {
hjk's avatar
hjk committed
857
858
        moveToStartOfLine();
        moveDown();
859
        moveToFirstNonBlankOnLine();
860
        finishMovement();
hjk's avatar
hjk committed
861
    } else if (key == Key_Home) {
hjk's avatar
hjk committed
862
        moveToStartOfLine();
hjk's avatar
hjk committed
863
864
        finishMovement();
    } else if (key == '$' || key == Key_End) {
865
        int submode = m_submode;
hjk's avatar
hjk committed
866
        moveToEndOfLine();
hjk's avatar
hjk committed
867
        finishMovement();
868
869
        if (submode == NoSubMode)
            m_desiredColumn = -1;
870
871
872
873
    } else if (key == ',') {
        // FIXME: use some other mechanism
        m_mode = PassingMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
874
875
876
877
    } else if (key == '.') {
        qDebug() << "REPEATING" << m_dotCommand;
        for (int i = count(); --i >= 0; )
            foreach (QChar c, m_dotCommand)
878
                handleKey(c.unicode(), c.unicode(), QString(c));
879
880
    } else if (key == '=') {
        m_submode = IndentSubMode;
881
    } else if (key == '%') {
882
        moveToMatchingParanthesis();
883
        finishMovement();
hjk's avatar
hjk committed
884
    } else if (key == 'a') {
hjk's avatar
hjk committed
885
        m_mode = InsertMode;
hjk's avatar
hjk committed
886
        recordBeginGroup();
hjk's avatar
hjk committed
887
        m_lastInsertion.clear();
888
889
        if (!atEndOfLine())
            moveRight();
890
        updateMiniBuffer();
hjk's avatar
hjk committed
891
    } else if (key == 'A') {
hjk's avatar
hjk committed
892
        m_mode = InsertMode;
hjk's avatar
hjk committed
893
894
        moveToEndOfLine();
        recordBeginGroup();
hjk's avatar
hjk committed
895
        m_lastInsertion.clear();
hjk's avatar
hjk committed
896
    } else if (key == 'b') {
897
        m_moveType = MoveExclusive;
hjk's avatar
hjk committed
898
        moveToWordBoundary(false, false);
hjk's avatar
hjk committed
899
900
        finishMovement();
    } else if (key == 'B') {
901
        m_moveType = MoveExclusive;