fakevimhandler.cpp 78.5 KB
Newer Older
1
/**************************************************************************
hjk's avatar
hjk committed
2 3 4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
hjk's avatar
hjk committed
6 7 8
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
9
** Commercial Usage
hjk's avatar
hjk committed
10
**
11 12 13 14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
hjk's avatar
hjk committed
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
**
18 19 20 21 22 23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
25 26
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
hjk's avatar
hjk committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

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

32 33
#include "fakevimconstants.h"

34 35 36 37
// 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
38 39


40 41 42 43 44 45 46 47 48 49 50 51 52
// Some conventions:
//
// Use 1 based line numbers and 0 based column numbers. Even though
// the 1 based line are not nice it matches vim's and QTextEdit's 'line'
// concepts.
//
// Do not pass QTextCursor etc around unless really needed. Convert
// early to  line/column.
//
// There is always a "current" cursor (m_tc). A current "region of interest"
// spans between m_anchor (== anchor()) and  m_tc.position() (== position())
// The value of m_tc.anchor() is not used.

53

hjk's avatar
hjk committed
54
#include <QtCore/QDebug>
55
#include <QtCore/QFile>
hjk's avatar
hjk committed
56
#include <QtCore/QObject>
57
#include <QtCore/QPointer>
hjk's avatar
hjk committed
58
#include <QtCore/QProcess>
hjk's avatar
hjk committed
59
#include <QtCore/QRegExp>
60
#include <QtCore/QTextStream>
hjk's avatar
hjk committed
61
#include <QtCore/QtAlgorithms>
hjk's avatar
hjk committed
62 63
#include <QtCore/QStack>

hjk's avatar
hjk committed
64
#include <QtGui/QApplication>
hjk's avatar
hjk committed
65 66 67 68 69 70
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
hjk's avatar
hjk committed
71
#include <QtGui/QTextDocumentFragment>
hjk's avatar
hjk committed
72
#include <QtGui/QTextEdit>
hjk's avatar
hjk committed
73 74


75 76 77 78 79 80 81
//#define DEBUG_KEY  1
#if DEBUG_KEY
#   define KEY_DEBUG(s) qDebug() << s
#else
#   define KEY_DEBUG(s)
#endif

82 83 84 85 86 87 88
//#define DEBUG_UNDO  1
#if DEBUG_UNDO
#   define UNDO_DEBUG(s) qDebug() << s
#else
#   define UNDO_DEBUG(s)
#endif

hjk's avatar
hjk committed
89
using namespace FakeVim::Internal;
90
using namespace FakeVim::Constants;
hjk's avatar
hjk committed
91

hjk's avatar
hjk committed
92 93 94 95 96 97 98

///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////

99 100 101 102 103 104 105 106 107
#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
108

109 110
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)

hjk's avatar
hjk committed
111 112 113 114
const int ParagraphSeparator = 0x00002029;

using namespace Qt;

hjk's avatar
hjk committed
115 116 117 118
enum Mode
{
    InsertMode,
    CommandMode,
119 120 121
    ExMode,
    SearchForwardMode,
    SearchBackwardMode,
hjk's avatar
hjk committed
122
};
hjk's avatar
hjk committed
123

hjk's avatar
hjk committed
124 125 126
enum SubMode
{
    NoSubMode,
127 128 129 130
    RegisterSubMode,   // used for "
    ChangeSubMode,     // used for c
    DeleteSubMode,     // used for d
    FilterSubMode,     // used for !
hjk's avatar
hjk committed
131
    ReplaceSubMode,    // used for R and r
132 133 134 135
    YankSubMode,       // used for y
    ShiftLeftSubMode,  // used for <
    ShiftRightSubMode, // used for >
    IndentSubMode,     // used for =
136
    ZSubMode,
hjk's avatar
hjk committed
137
};
hjk's avatar
hjk committed
138

hjk's avatar
hjk committed
139 140
enum SubSubMode
{
hjk's avatar
hjk committed
141 142
    // typically used for things that require one more data item
    // and are 'nested' behind a mode
hjk's avatar
hjk committed
143
    NoSubSubMode,
144 145
    FtSubSubMode,       // used for f, F, t, T
    MarkSubSubMode,     // used for m
146
    BackTickSubSubMode, // used for `
hjk's avatar
hjk committed
147
    TickSubSubMode,     // used for '
hjk's avatar
hjk committed
148 149
};

150 151 152 153 154 155 156 157
enum VisualMode
{
    NoVisualMode,
    VisualCharMode,
    VisualLineMode,
    VisualBlockMode,
};

hjk's avatar
hjk committed
158 159 160 161
enum MoveType
{
    MoveExclusive,
    MoveInclusive,
hjk's avatar
hjk committed
162
    MoveLineWise,
hjk's avatar
hjk committed
163 164
};

165 166
struct EditOperation
{
hjk's avatar
hjk committed
167 168 169 170 171
    EditOperation() : position(-1), itemCount(0) {}
    int position;
    int itemCount; // used to combine several operations
    QString from;
    QString to;
172 173
};

hjk's avatar
hjk committed
174
QDebug &operator<<(QDebug &ts, const EditOperation &op)
hjk's avatar
hjk committed
175
{
hjk's avatar
hjk committed
176 177
    if (op.itemCount > 0) {
        ts << "\n  EDIT BLOCK WITH " << op.itemCount << " ITEMS";
hjk's avatar
hjk committed
178
    } else {
hjk's avatar
hjk committed
179
        ts << "\n  EDIT AT " << op.position
hjk's avatar
hjk committed
180
           << "  FROM   " << op.from << "   TO    " << op.to;
hjk's avatar
hjk committed
181 182 183 184
    }
    return ts;
}

185 186 187 188 189 190 191
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
192 193 194 195 196 197
int lineCount(const QString &text)
{
    //return text.count(QChar(ParagraphSeparator));
    return text.count(QChar('\n'));
}

198 199 200 201 202 203 204
enum EventResult
{
    EventHandled,
    EventUnhandled,
    EventPassedToCore
};

hjk's avatar
hjk committed
205 206 207
class FakeVimHandler::Private
{
public:
208
    Private(FakeVimHandler *parent, QWidget *widget);
hjk's avatar
hjk committed
209

210 211
    EventResult handleEvent(QKeyEvent *ev);
    bool wantsOverride(QKeyEvent *ev);
212
    void handleExCommand(const QString &cmd);
hjk's avatar
hjk committed
213

214 215 216
    void setupWidget();
    void restoreWidget();

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

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

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

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

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

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

hjk's avatar
hjk committed
262
    void moveToFirstNonBlankOnLine();
263
    void moveToDesiredColumn();
hjk's avatar
hjk committed
264
    void moveToNextWord(bool simple);
265
    void moveToMatchingParanthesis();
hjk's avatar
hjk committed
266
    void moveToWordBoundary(bool simple, bool forward);
hjk's avatar
hjk committed
267 268 269 270

    // to reduce line noise
    typedef QTextCursor::MoveOperation MoveOperation;
    typedef QTextCursor::MoveMode MoveMode;
hjk's avatar
hjk committed
271 272 273 274 275 276 277
    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
278
    void setAnchor() { m_anchor = m_tc.position(); }
279 280
    void setAnchor(int position) { m_anchor = position; }
    void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
hjk's avatar
hjk committed
281

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

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

288
    void enterInsertMode();
289
    void enterCommandMode();
290
    void enterExMode();
hjk's avatar
hjk committed
291 292
    void showRedMessage(const QString &msg);
    void showBlackMessage(const QString &msg);
293
    void notImplementedYet();
294 295
    void updateMiniBuffer();
    void updateSelection();
hjk's avatar
hjk committed
296
    void quit();
297
    QWidget *editor() const;
298 299
    QChar characterAtCursor() const
        { return m_tc.document()->characterAt(m_tc.position()); }
300

301 302 303
public:
    QTextEdit *m_textedit;
    QPlainTextEdit *m_plaintextedit;
304
    bool m_wasReadOnly; // saves read-only state of document
305

hjk's avatar
hjk committed
306 307
    FakeVimHandler *q;
    Mode m_mode;
308
    bool m_passing; // let the core see the next event
hjk's avatar
hjk committed
309
    SubMode m_submode;
hjk's avatar
hjk committed
310 311
    SubSubMode m_subsubmode;
    int m_subsubdata;
hjk's avatar
hjk committed
312 313
    QString m_input;
    QTextCursor m_tc;
hjk's avatar
hjk committed
314
    int m_anchor; 
hjk's avatar
hjk committed
315 316
    QHash<int, QString> m_registers;
    int m_register;
hjk's avatar
hjk committed
317 318
    QString m_mvcount;
    QString m_opcount;
hjk's avatar
hjk committed
319
    MoveType m_moveType;
hjk's avatar
hjk committed
320 321 322

    bool m_fakeEnd;

323 324
    bool isSearchMode() const
        { return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
hjk's avatar
hjk committed
325
    int m_gflag;  // whether current command started with 'g'
326

hjk's avatar
hjk committed
327
    QString m_commandBuffer;
328 329
    QString m_currentFileName;
    QString m_currentMessage;
hjk's avatar
hjk committed
330

331
    bool m_lastSearchForward;
hjk's avatar
hjk committed
332
    QString m_lastInsertion;
hjk's avatar
hjk committed
333

334
    // undo handling
hjk's avatar
hjk committed
335
    void recordOperation(const EditOperation &op);
336
    void recordInsert(int position, const QString &data);
hjk's avatar
hjk committed
337
    void recordRemove(int position, const QString &data);
338
    void recordRemove(int position, int length);
hjk's avatar
hjk committed
339 340 341

    void recordRemoveNextChar();
    void recordInsertText(const QString &data);
hjk's avatar
hjk committed
342
    QString recordRemoveSelectedText();
343
    void recordPosition();
hjk's avatar
hjk committed
344 345
    void recordBeginGroup();
    void recordEndGroup();
hjk's avatar
hjk committed
346 347
    int anchor() const { return m_anchor; }
    int position() const { return m_tc.position(); }
348 349
    QString selectedText() const;

350 351 352 353
    void undo();
    void redo();
    QStack<EditOperation> m_undoStack;
    QStack<EditOperation> m_redoStack;
hjk's avatar
hjk committed
354
    QStack<int> m_undoGroupStack;
355
    QMap<int, int> m_undoCursorPosition;
356

hjk's avatar
hjk committed
357 358 359
    // extra data for '.'
    QString m_dotCommand;

hjk's avatar
hjk committed
360 361 362 363 364
    // extra data for ';'
    QString m_semicolonCount;
    int m_semicolonType;  // 'f', 'F', 't', 'T'
    int m_semicolonKey;

hjk's avatar
hjk committed
365
    // history for '/'
hjk's avatar
hjk committed
366 367 368 369
    QString lastSearchString() const;
    QStringList m_searchHistory;
    int m_searchHistoryIndex;

hjk's avatar
hjk committed
370
    // history for ':'
hjk's avatar
hjk committed
371 372
    QStringList m_commandHistory;
    int m_commandHistoryIndex;
hjk's avatar
hjk committed
373

hjk's avatar
hjk committed
374
    // visual line mode
375 376 377
    void enterVisualMode(VisualMode visualMode);
    void leaveVisualMode();
    VisualMode m_visualMode;
hjk's avatar
hjk committed
378

379 380 381
    // marks as lines
    QHash<int, int> m_marks;

hjk's avatar
hjk committed
382 383
    // vi style configuration
    QHash<QString, QString> m_config;
384 385

    // for restoring cursor position
hjk's avatar
hjk committed
386
    int m_savedYankPosition;
387
    int m_desiredColumn;
388 389

    QPointer<QObject> m_extraData;
390
    int m_cursorWidth;
391 392 393 394

    void recordJump();
    QList<int> m_jumpListUndo;
    QList<int> m_jumpListRedo;
hjk's avatar
hjk committed
395 396

    QList<QTextEdit::ExtraSelection> m_searchSelections;
hjk's avatar
hjk committed
397 398
};

399
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
hjk's avatar
hjk committed
400 401
{
    q = parent;
hjk's avatar
hjk committed
402

403 404 405
    m_textedit = qobject_cast<QTextEdit *>(widget);
    m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);

hjk's avatar
hjk committed
406
    m_mode = CommandMode;
hjk's avatar
hjk committed
407 408
    m_submode = NoSubMode;
    m_subsubmode = NoSubSubMode;
409
    m_passing = false;
hjk's avatar
hjk committed
410
    m_fakeEnd = false;
411
    m_lastSearchForward = true;
hjk's avatar
hjk committed
412
    m_register = '"';
hjk's avatar
hjk committed
413
    m_gflag = false;
414
    m_visualMode = NoVisualMode;
415
    m_desiredColumn = 0;
hjk's avatar
hjk committed
416
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
417 418
    m_anchor = 0;
    m_savedYankPosition = 0;
419
    m_cursorWidth = EDITOR(cursorWidth());
hjk's avatar
hjk committed
420 421

    m_config[ConfigStartOfLine] = ConfigOn;
hjk's avatar
hjk committed
422
    m_config[ConfigHlSearch]    = ConfigOn;
423
    m_config[ConfigTabStop]     = "8";
424
    m_config[ConfigSmartTab]    = ConfigOff;
425
    m_config[ConfigShiftWidth]  = "8";
426
    m_config[ConfigExpandTab]   = ConfigOff;
427
    m_config[ConfigAutoIndent]  = ConfigOff;
hjk's avatar
hjk committed
428 429
}

430
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
hjk's avatar
hjk committed
431
{
432 433 434
    const int key = ev->key();
    const int mods = ev->modifiers();
    KEY_DEBUG("SHORTCUT OVERRIDE" << key << "  PASSING: " << m_passing);
435

436
    if (key == Key_Escape) {
hjk's avatar
hjk committed
437
        // Not sure this feels good. People often hit Esc several times
438 439 440 441 442 443 444 445 446 447 448 449 450
        if (m_visualMode == NoVisualMode && m_mode == CommandMode)
            return false;
        return true;
    }

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

457 458 459 460 461 462 463 464 465 466
    // Let other shortcuts trigger
    return false;
}

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

hjk's avatar
hjk committed
467
    if (key == Key_Shift || key == Key_Alt || key == Key_Control
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
            || key == Key_Alt || key == Key_AltGr || key == Key_Meta)
    {
        KEY_DEBUG("PLAIN MODIFIER");
        return EventUnhandled;
    }

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

    // Fake "End of line"
488
    m_tc = EDITOR(textCursor());
489
    m_tc.setVisualNavigation(true);
490

hjk's avatar
hjk committed
491
    if (m_fakeEnd)
hjk's avatar
hjk committed
492
        moveRight();
hjk's avatar
hjk committed
493

494
    if ((mods & Qt::ControlModifier) != 0) {
hjk's avatar
hjk committed
495
        key += 256;
496
        key += 32; // make it lower case
497
    } else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
498 499
        key += 32;
    }
500 501 502 503 504 505

    m_undoCursorPosition[EDITOR(document())->revision()] = m_tc.position();
    if (m_mode == InsertMode)
        m_tc.joinPreviousEditBlock();
    else
        m_tc.beginEditBlock();
506
    EventResult result = handleKey(key, um, ev->text());
507
    m_tc.endEditBlock();
hjk's avatar
hjk committed
508 509

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

hjk's avatar
hjk committed
512
    if (m_fakeEnd)
hjk's avatar
hjk committed
513
        moveLeft();
hjk's avatar
hjk committed
514

515 516
    EDITOR(setTextCursor(m_tc));
    EDITOR(ensureCursorVisible());
517
    return result;
hjk's avatar
hjk committed
518 519
}

520 521 522
void FakeVimHandler::Private::setupWidget()
{
    enterCommandMode();
523 524
    EDITOR(installEventFilter(q));
    //EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
525 526 527 528 529
    if (m_textedit) {
        m_textedit->setLineWrapMode(QTextEdit::NoWrap);
    } else if (m_plaintextedit) {
        m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
    }
530
    m_wasReadOnly = EDITOR(isReadOnly());
531
    //EDITOR(setReadOnly(true)); 
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546

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

547 548 549 550 551 552 553 554
    showBlackMessage("vi emulation mode.");
    updateMiniBuffer();
}

void FakeVimHandler::Private::restoreWidget()
{
    //showBlackMessage(QString());
    //updateMiniBuffer();
555 556
    EDITOR(removeEventFilter(q));
    EDITOR(setReadOnly(m_wasReadOnly));
557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
    
    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();
574 575
}

576 577
EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
    const QString &text)
hjk's avatar
hjk committed
578
{
579 580
    //qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
    //qDebug() << "\nUNDO: " << m_undoStack << "\nREDO: " << m_redoStack;
hjk's avatar
hjk committed
581
    if (m_mode == InsertMode)
582
        return handleInsertMode(key, unmodified, text);
583
    if (m_mode == CommandMode)
584
        return handleCommandMode(key, unmodified, text);
585
    if (m_mode == ExMode || m_mode == SearchForwardMode
586
            || m_mode == SearchBackwardMode)
587
        return handleMiniBufferModes(key, unmodified, text);
588
    return EventUnhandled;
hjk's avatar
hjk committed
589 590
}

hjk's avatar
hjk committed
591
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
hjk's avatar
hjk committed
592
{
593
    //qDebug() << "ANCHOR: " << m_anchor;
hjk's avatar
hjk committed
594
    if (m_submode == FilterSubMode) {
hjk's avatar
hjk committed
595 596
        int beginLine = lineForPosition(anchor());
        int endLine = lineForPosition(position());
597
        setPosition(qMin(anchor(), position()));
598
        enterExMode();
hjk's avatar
hjk committed
599 600 601 602 603 604 605
        m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
        m_commandHistory.append(QString());
        m_commandHistoryIndex = m_commandHistory.size() - 1;
        updateMiniBuffer();
        return;
    }

606 607 608
    if (m_visualMode != NoVisualMode)
        m_marks['>'] = m_tc.position();

hjk's avatar
hjk committed
609
    if (m_submode == ChangeSubMode) {
610 611
        if (m_moveType == MoveInclusive)
            moveRight(); // correction
612 613
        if (anchor() >= position())
           m_anchor++;
hjk's avatar
hjk committed
614 615
        if (!dotCommand.isEmpty())
            m_dotCommand = "c" + dotCommand;
hjk's avatar
hjk committed
616
        QString text = recordRemoveSelectedText();
617
        //qDebug() << "CHANGING TO INSERT MODE" << text;
hjk's avatar
hjk committed
618
        m_registers[m_register] = text;
hjk's avatar
hjk committed
619 620 621
        m_mode = InsertMode;
        m_submode = NoSubMode;
    } else if (m_submode == DeleteSubMode) {
622
        if (m_moveType == MoveInclusive)
623
            moveRight(); // correction
624 625
        if (anchor() >= position())
           m_anchor++;
hjk's avatar
hjk committed
626 627
        if (!dotCommand.isEmpty())
            m_dotCommand = "d" + dotCommand;
hjk's avatar
hjk committed
628 629
        m_registers[m_register] = recordRemoveSelectedText();
        recordEndGroup();
hjk's avatar
hjk committed
630
        m_submode = NoSubMode;
631
        if (atEndOfLine())
hjk's avatar
hjk committed
632
            moveLeft();
633
    } else if (m_submode == YankSubMode) {
hjk's avatar
hjk committed
634
        m_registers[m_register] = selectedText();
635
        setPosition(m_savedYankPosition);
636
        m_submode = NoSubMode;
637 638
    } else if (m_submode == ReplaceSubMode) {
        m_submode = NoSubMode;
639
    } else if (m_submode == IndentSubMode) {
hjk's avatar
hjk committed
640
        indentRegion();
641
        m_submode = NoSubMode;
642
    } else if (m_submode == ShiftRightSubMode) {
643
        shiftRegionRight(1);
644 645 646
        m_submode = NoSubMode;
        updateMiniBuffer();
    } else if (m_submode == ShiftLeftSubMode) {
647
        shiftRegionLeft(1);
648 649
        m_submode = NoSubMode;
        updateMiniBuffer();
hjk's avatar
hjk committed
650
    }
651

hjk's avatar
hjk committed
652
    m_moveType = MoveInclusive;
hjk's avatar
hjk committed
653 654
    m_mvcount.clear();
    m_opcount.clear();
hjk's avatar
hjk committed
655
    m_gflag = false;
hjk's avatar
hjk committed
656 657
    m_register = '"';
    m_tc.clearSelection();
658 659

    updateSelection();
hjk's avatar
hjk committed
660
    updateMiniBuffer();
661
    m_desiredColumn = leftDist();
hjk's avatar
hjk committed
662 663
}

664 665
void FakeVimHandler::Private::updateSelection()
{
hjk's avatar
hjk committed
666
    QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
667 668 669 670
    if (m_visualMode != NoVisualMode) {
        QTextEdit::ExtraSelection sel;
        sel.cursor = m_tc;
        sel.format = m_tc.blockCharFormat();
671 672 673 674
#if 0
        sel.format.setFontWeight(QFont::Bold);
        sel.format.setFontUnderline(true);
#else
hjk's avatar
hjk committed
675 676
        sel.format.setForeground(Qt::white);
        sel.format.setBackground(Qt::black);
677
#endif
678 679 680
        int cursorPos = m_tc.position();
        int anchorPos = m_marks['<'];
        //qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
681
        if (m_visualMode == VisualCharMode) {
682
            sel.cursor.setPosition(anchorPos, KeepAnchor);
683 684
            selections.append(sel);
        } else if (m_visualMode == VisualLineMode) {
685
            sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
686
            sel.cursor.movePosition(StartOfLine, MoveAnchor);
687
            sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
688 689 690
            sel.cursor.movePosition(EndOfLine, KeepAnchor);
            selections.append(sel);
        } else if (m_visualMode == VisualBlockMode) {
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711
            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);
            }
712 713
        }
    }
714
    //qDebug() << "SELECTION: " << selections;
715
    emit q->selectionChanged(selections);
716 717
}

hjk's avatar
hjk committed
718
void FakeVimHandler::Private::updateMiniBuffer()
hjk's avatar
hjk committed
719
{
hjk's avatar
hjk committed
720
    QString msg;
721 722
    if (m_passing) {
        msg = "-- PASSING --  ";
723
    } else if (!m_currentMessage.isEmpty()) {
hjk's avatar
hjk committed
724 725
        msg = m_currentMessage;
        m_currentMessage.clear();
hjk's avatar
hjk committed
726 727 728 729 730 731 732 733
    } 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 --";
        }
734 735
    } else if (m_mode == InsertMode) {
        msg = "-- INSERT --";
hjk's avatar
hjk committed
736
    } else {
737
        if (m_mode == SearchForwardMode)
738
            msg += '/';
739
        else if (m_mode == SearchBackwardMode)
740
            msg += '?';
741
        else if (m_mode == ExMode)
742
            msg += ':';
hjk's avatar
hjk committed
743
        foreach (QChar c, m_commandBuffer) {
744 745 746 747 748 749
            if (c.unicode() < 32) {
                msg += '^';
                msg += QChar(c.unicode() + 64);
            } else {
                msg += c;
            }
hjk's avatar
hjk committed
750
        }
hjk's avatar
hjk committed
751
        if (!msg.isEmpty() && m_mode != CommandMode)
hjk's avatar
hjk committed
752
            msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
hjk's avatar
hjk committed
753
    }
754

hjk's avatar
hjk committed
755 756 757 758 759
    emit q->commandBufferChanged(msg);

    int linesInDoc = linesInDocument();
    int l = cursorLineInDocument();
    QString status;
hjk's avatar
hjk committed
760
    QString pos = tr("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
hjk's avatar
hjk committed
761
    status += tr("%1").arg(pos, -10);
hjk's avatar
hjk committed
762
    // FIXME: physical "-" logical
hjk's avatar
hjk committed
763
    if (linesInDoc != 0) {
hjk's avatar
hjk committed
764 765
        status += tr("%1").arg(l * 100 / linesInDoc, 4);
        status += "%";
hjk's avatar
hjk committed
766
    } else {
hjk's avatar
hjk committed
767
        status += "All";
hjk's avatar
hjk committed
768
    }
hjk's avatar
hjk committed
769
    emit q->statusDataChanged(status);
hjk's avatar
hjk committed
770 771
}

hjk's avatar
hjk committed
772
void FakeVimHandler::Private::showRedMessage(const QString &msg)
hjk's avatar
hjk committed
773
{
774 775
    //qDebug() << "MSG: " << msg;
    m_currentMessage = msg;
hjk's avatar
hjk committed
776
    updateMiniBuffer();
hjk's avatar
hjk committed
777 778
}

hjk's avatar
hjk committed
779 780 781 782 783 784 785
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
    //qDebug() << "MSG: " << msg;
    m_commandBuffer = msg;
    updateMiniBuffer();
}

786 787 788 789 790 791
void FakeVimHandler::Private::notImplementedYet()
{
    showRedMessage("Not implemented in FakeVim");
    updateMiniBuffer();
}

792
EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
793
    const QString &text)
hjk's avatar
hjk committed
794
{
795
    EventResult handled = EventHandled;
796

hjk's avatar
hjk committed
797 798 799 800
    if (m_submode == RegisterSubMode) {
        m_register = key;
        m_submode = NoSubMode;
    } else if (m_submode == ChangeSubMode && key == 'c') {
hjk's avatar
hjk committed
801 802 803
        moveToStartOfLine();
        setAnchor();
        moveDown(count());
804
        m_moveType = MoveLineWise;
hjk's avatar
hjk committed
805
        finishMovement("c");
hjk's avatar
hjk committed
806
    } else if (m_submode == DeleteSubMode && key == 'd') {
hjk's avatar
hjk committed
807
        moveToStartOfLine();