Files
qt-creator/src/plugins/fakevim/fakevimhandler.cpp
Martin Aumüller df9335c542 fakevim: fix 'P' at end of line
previously, the insertion point would have stayed just in front of the last character instead of in
front of the last but first character

Merge-request: 96
Reviewed-by: hjk <qtc-committer@nokia.com>
2010-01-05 18:48:52 +01:00

3176 lines
103 KiB
C++
Executable File

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** 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.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "fakevimhandler.h"
//
// ATTENTION:
//
// 1 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.
//
// 2 There are a few auto tests located in ../../../tests/auto/fakevim.
// Commands that are covered there are marked as "// tested" below.
//
// 3 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.
//
#include <utils/qtcassert.h>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QProcess>
#include <QtCore/QRegExp>
#include <QtCore/QTextStream>
#include <QtCore/QtAlgorithms>
#include <QtCore/QStack>
#include <QtGui/QApplication>
#include <QtGui/QKeyEvent>
#include <QtGui/QLineEdit>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QScrollBar>
#include <QtGui/QTextBlock>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocumentFragment>
#include <QtGui/QTextEdit>
#include <climits>
#include <ctype.h>
//#define DEBUG_KEY 1
#if DEBUG_KEY
# define KEY_DEBUG(s) qDebug() << s
#else
# define KEY_DEBUG(s)
#endif
//#define DEBUG_UNDO 1
#if DEBUG_UNDO
# define UNDO_DEBUG(s) qDebug() << << m_tc.document()->availableUndoSteps() << s
#else
# define UNDO_DEBUG(s)
#endif
using namespace Utils;
namespace FakeVim {
namespace Internal {
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
#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
#define StartOfDocument QTextCursor::Start
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
const int ParagraphSeparator = 0x00002029;
using namespace Qt;
enum Mode
{
InsertMode,
CommandMode,
ExMode,
SearchForwardMode,
SearchBackwardMode,
};
enum SubMode
{
NoSubMode,
ChangeSubMode, // used for c
DeleteSubMode, // used for d
FilterSubMode, // used for !
IndentSubMode, // used for =
RegisterSubMode, // used for "
ReplaceSubMode, // used for R and r
ShiftLeftSubMode, // used for <
ShiftRightSubMode, // used for >
WindowSubMode, // used for Ctrl-w
YankSubMode, // used for y
ZSubMode, // used for z
CapitalZSubMode // used for Z
};
enum SubSubMode
{
// typically used for things that require one more data item
// and are 'nested' behind a mode
NoSubSubMode,
FtSubSubMode, // used for f, F, t, T
MarkSubSubMode, // used for m
BackTickSubSubMode, // used for `
TickSubSubMode, // used for '
};
enum VisualMode
{
NoVisualMode,
VisualCharMode,
VisualLineMode,
VisualBlockMode,
};
enum MoveType
{
MoveExclusive,
MoveInclusive,
MoveLineWise,
};
enum RangeMode
{
RangeCharMode, // v
RangeLineMode, // V
RangeBlockMode, // Ctrl-v
RangeBlockAndTailMode, // Ctrl-v for D and X
};
enum EventResult
{
EventHandled,
EventUnhandled,
EventPassedToCore
};
struct Indentation
{
Indentation(int p, int l) : physical(p), logical(l) {}
int physical; // number of characters
int logical;
};
struct CursorPosition
{
// for jump history
CursorPosition() : position(-1), scrollLine(-1) {}
CursorPosition(int pos, int line) : position(pos), scrollLine(line) {}
int position; // Position in document
int scrollLine; // First visible line
};
struct Register
{
Register() : rangemode(RangeCharMode) {}
Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
QString contents;
RangeMode rangemode;
};
struct Range
{
Range()
: beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
Range(int b, int e, RangeMode m = RangeCharMode)
: beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
{}
QString toString() const
{
return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
.arg(rangemode);
}
int beginPos;
int endPos;
RangeMode rangemode;
};
QDebug &operator<<(QDebug &ts, const QList<QTextEdit::ExtraSelection> &sels)
{
foreach (QTextEdit::ExtraSelection sel, sels)
ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
return ts;
}
QString quoteUnprintable(const QString &ba)
{
QString res;
for (int i = 0, n = ba.size(); i != n; ++i) {
QChar c = ba.at(i);
if (c.isPrint())
res += c;
else
res += QString("\\x%1").arg(c.unicode(), 2, 16);
}
return res;
}
inline QString msgE20MarkNotSet(const QString &text)
{
return FakeVimHandler::tr("E20: Mark '%1' not set").arg(text);
}
class FakeVimHandler::Private
{
public:
Private(FakeVimHandler *parent, QWidget *widget);
EventResult handleEvent(QKeyEvent *ev);
bool wantsOverride(QKeyEvent *ev);
void handleCommand(const QString &cmd); // sets m_tc + handleExCommand
void handleExCommand(const QString &cmd);
void fixMarks(int positionAction, int positionChange); //Updates marks positions by the difference in positionChange
void installEventFilter();
void setupWidget();
void restoreWidget();
friend class FakeVimHandler;
static int shift(int key) { return key + 32; }
static int control(int key) { return key + 256; }
void init();
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);
void finishMovement(const QString &text = QString());
void search(const QString &needle, bool forward);
void highlightMatches(const QString &needle);
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(); }
int leftDist() const { return m_tc.position() - m_tc.block().position(); }
int rightDist() const { return m_tc.block().length() - leftDist() - 1; }
bool atEndOfLine() const
{ return m_tc.atBlockEnd() && m_tc.block().length() > 1; }
int lastPositionInDocument() const; // last valid pos in doc
int firstPositionInLine(int line) const; // 1 based line, 0 based pos
int lastPositionInLine(int line) const; // 1 based line, 0 based pos
int lineForPosition(int pos) const; // 1 based line, 0 based pos
QString lineContents(int line) const; // 1 based line
void setLineContents(int line, const QString &contents) const; // 1 based line
int linesOnScreen() const;
int columnsOnScreen() const;
int linesInDocument() const;
// all zero-based counting
int cursorLineOnScreen() const;
int cursorLineInDocument() const;
int cursorColumnInDocument() const;
int firstVisibleLineInDocument() const;
void scrollToLineInDocument(int line);
void scrollUp(int count);
void scrollDown(int count) { scrollUp(-count); }
CursorPosition cursorPosition() const
{ return CursorPosition(position(), firstVisibleLineInDocument()); }
void setCursorPosition(const CursorPosition &p)
{ setPosition(p.position); scrollToLineInDocument(p.scrollLine); }
// helper functions for indenting
bool isElectricCharacter(QChar c) const
{ return c == '{' || c == '}' || c == '#'; }
void indentRegion(QChar lastTyped = QChar());
void shiftRegionLeft(int repeat = 1);
void shiftRegionRight(int repeat = 1);
void moveToFirstNonBlankOnLine();
void moveToTargetColumn();
void setTargetColumn() {
m_targetColumn = leftDist();
//qDebug() << "TARGET: " << m_targetColumn;
}
void moveToNextWord(bool simple);
void moveToMatchingParanthesis();
void moveToWordBoundary(bool simple, bool forward);
// to reduce line noise
void moveToEndOfDocument() { m_tc.movePosition(EndOfDocument, MoveAnchor); }
void moveToStartOfLine();
void moveToEndOfLine();
void moveBehindEndOfLine();
void moveUp(int n = 1) { moveDown(-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); }
void setAnchor() { m_anchor = m_tc.position(); }
void setAnchor(int position) { m_anchor = position; }
void setPosition(int position) { m_tc.setPosition(position, MoveAnchor); }
void handleFfTt(int key);
// helper function for handleExCommand. return 1 based line index.
int readLineCode(QString &cmd);
void selectRange(int beginLine, int endLine);
void enterInsertMode();
void enterCommandMode();
void enterExMode();
void showRedMessage(const QString &msg);
void showBlackMessage(const QString &msg);
void notImplementedYet();
void updateMiniBuffer();
void updateSelection();
QWidget *editor() const;
QChar characterAtCursor() const
{ return m_tc.document()->characterAt(m_tc.position()); }
void beginEditBlock() { UNDO_DEBUG("BEGIN EDIT BLOCK"); m_tc.beginEditBlock(); }
void beginEditBlock(int pos) { setUndoPosition(pos); beginEditBlock(); }
void endEditBlock() { UNDO_DEBUG("END EDIT BLOCK"); m_tc.endEditBlock(); }
void joinPreviousEditBlock() { UNDO_DEBUG("JOIN EDIT BLOCK"); m_tc.joinPreviousEditBlock(); }
// this asks the layer above (e.g. the fake vim plugin or the
// stand-alone test application to handle the command)
void passUnknownExCommand(const QString &cmd);
bool isVisualMode() const { return m_visualMode != NoVisualMode; }
bool isNoVisualMode() const { return m_visualMode == NoVisualMode; }
bool isVisualCharMode() const { return m_visualMode == VisualCharMode; }
bool isVisualLineMode() const { return m_visualMode == VisualLineMode; }
bool isVisualBlockMode() const { return m_visualMode == VisualBlockMode; }
public:
QTextEdit *m_textedit;
QPlainTextEdit *m_plaintextedit;
bool m_wasReadOnly; // saves read-only state of document
FakeVimHandler *q;
Mode m_mode;
bool m_passing; // let the core see the next event
SubMode m_submode;
SubSubMode m_subsubmode;
int m_subsubdata;
QString m_input;
QTextCursor m_tc;
int m_oldPosition; // copy from last event to check for external changes
int m_anchor;
static QHash<int, Register> m_registers;
int m_register;
QString m_mvcount;
QString m_opcount;
MoveType m_movetype;
RangeMode m_rangemode;
bool m_fakeEnd;
bool isSearchMode() const
{ return m_mode == SearchForwardMode || m_mode == SearchBackwardMode; }
int m_gflag; // whether current command started with 'g'
QString m_commandBuffer;
QString m_currentFileName;
QString m_currentMessage;
bool m_lastSearchForward;
QString m_lastInsertion;
int anchor() const { return m_anchor; }
int position() const { return m_tc.position(); }
void removeSelectedText();
void removeText(const Range &range);
QString selectedText() const { return text(Range(position(), anchor())); }
QString text(const Range &range) const;
void yankSelectedText();
void yankText(const Range &range, int toregister = '"');
void pasteText(bool afterCursor);
// undo handling
void undo();
void redo();
void setUndoPosition(int pos);
QMap<int, int> m_undoCursorPosition; // revision -> position
bool m_beginEditBlock;
// extra data for '.'
void replay(const QString &text, int count);
void setDotCommand(const QString &cmd) { m_dotCommand = cmd; }
void setDotCommand(const QString &cmd, int n) { m_dotCommand = cmd.arg(n); }
QString m_dotCommand;
bool m_inReplay; // true if we are executing a '.'
// extra data for ';'
QString m_semicolonCount;
int m_semicolonType; // 'f', 'F', 't', 'T'
int m_semicolonKey;
// history for '/'
QString lastSearchString() const;
static QStringList m_searchHistory;
int m_searchHistoryIndex;
// history for ':'
static QStringList m_commandHistory;
int m_commandHistoryIndex;
// visual line mode
void enterVisualMode(VisualMode visualMode);
void leaveVisualMode();
VisualMode m_visualMode;
// marks as lines
QHash<int, int> m_marks;
QString m_oldNeedle;
// vi style configuration
QVariant config(int code) const { return theFakeVimSetting(code)->value(); }
bool hasConfig(int code) const { return config(code).toBool(); }
bool hasConfig(int code, const char *value) const // FIXME
{ return config(code).toString().contains(value); }
// for restoring cursor position
int m_savedYankPosition;
int m_targetColumn;
int m_cursorWidth;
// auto-indent
QString tabExpand(int len) const;
Indentation indentation(const QString &line) const;
void insertAutomaticIndentation(bool goingDown);
bool removeAutomaticIndentation(); // true if something removed
// number of autoindented characters
int m_justAutoIndented;
void handleStartOfLine();
void recordJump();
void recordNewUndo();
QVector<CursorPosition> m_jumpListUndo;
QVector<CursorPosition> m_jumpListRedo;
QList<QTextEdit::ExtraSelection> m_searchSelections;
};
QStringList FakeVimHandler::Private::m_searchHistory;
QStringList FakeVimHandler::Private::m_commandHistory;
QHash<int, Register> FakeVimHandler::Private::m_registers;
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
{
q = parent;
m_textedit = qobject_cast<QTextEdit *>(widget);
m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
init();
}
void FakeVimHandler::Private::init()
{
m_mode = CommandMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_passing = false;
m_fakeEnd = false;
m_lastSearchForward = true;
m_register = '"';
m_gflag = false;
m_visualMode = NoVisualMode;
m_targetColumn = 0;
m_movetype = MoveInclusive;
m_anchor = 0;
m_savedYankPosition = 0;
m_cursorWidth = EDITOR(cursorWidth());
m_inReplay = false;
m_justAutoIndented = 0;
m_rangemode = RangeCharMode;
m_beginEditBlock = true;
}
bool FakeVimHandler::Private::wantsOverride(QKeyEvent *ev)
{
const int key = ev->key();
const int mods = ev->modifiers();
KEY_DEBUG("SHORTCUT OVERRIDE" << key << " PASSING: " << m_passing);
if (key == Key_Escape || (mods == Qt::ControlModifier && key == Key_BracketLeft)) {
// Not sure this feels good. People often hit Esc several times
if (isNoVisualMode() && 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 Locator
if (m_passing) {
KEY_DEBUG(" PASSING CTRL KEY");
// We get called twice on the same key
//m_passing = false;
return false;
}
KEY_DEBUG(" NOT PASSING CTRL KEY");
//updateMiniBuffer();
return true;
}
// 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();
if (key == Key_Shift || key == Key_Alt || key == Key_Control
|| 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...";
// return EventHandled;
//}
m_passing = false;
updateMiniBuffer();
KEY_DEBUG(" PASS TO CORE");
return EventPassedToCore;
}
// Fake "End of line"
m_tc = EDITOR(textCursor());
// Position changed externally
if (m_tc.position() != m_oldPosition) {
setTargetColumn();
if (m_mode == InsertMode) {
int dist = m_tc.position() - m_oldPosition;
// Try to compensate for code completion
if (dist > 0 && dist <= cursorColumnInDocument()) {
Range range(m_oldPosition, m_tc.position());
m_lastInsertion.append(text(range));
}
}
}
m_tc.setVisualNavigation(true);
if (m_fakeEnd)
moveRight();
if ((mods & Qt::ControlModifier) != 0) {
key += 256;
key += 32; // make it lower case
} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
key += 32;
}
//if (m_mode == InsertMode)
// joinPreviousEditBlock();
//else
// beginEditBlock();
EventResult result = handleKey(key, um, ev->text());
//endEditBlock();
// We fake vi-style end-of-line behaviour
m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
if (m_fakeEnd)
moveLeft();
EDITOR(setTextCursor(m_tc));
m_oldPosition = m_tc.position();
return result;
}
void FakeVimHandler::Private::installEventFilter()
{
EDITOR(installEventFilter(q));
}
void FakeVimHandler::Private::setupWidget()
{
enterCommandMode();
//EDITOR(setCursorWidth(QFontMetrics(ed->font()).width(QChar('x')));
if (m_textedit) {
m_textedit->setLineWrapMode(QTextEdit::NoWrap);
} else if (m_plaintextedit) {
m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
}
m_wasReadOnly = EDITOR(isReadOnly());
//EDITOR(setReadOnly(true));
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();
}
//showBlackMessage("vi emulation mode. Type :q to leave. Use , Ctrl-R to trigger run.");
updateMiniBuffer();
}
void FakeVimHandler::Private::restoreWidget()
{
//showBlackMessage(QString());
//updateMiniBuffer();
//EDITOR(removeEventFilter(q));
EDITOR(setReadOnly(m_wasReadOnly));
EDITOR(setCursorWidth(m_cursorWidth));
EDITOR(setOverwriteMode(false));
if (isVisualLineMode()) {
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 (isVisualCharMode()) {
m_tc = EDITOR(textCursor());
m_tc.setPosition(m_marks['<'], MoveAnchor);
m_tc.setPosition(m_marks['>'], KeepAnchor);
EDITOR(setTextCursor(m_tc));
}
m_visualMode = NoVisualMode;
updateSelection();
}
EventResult FakeVimHandler::Private::handleKey(int key, int unmodified,
const QString &text)
{
//qDebug() << " CURSOR POS: " << m_undoCursorPosition;
setUndoPosition(m_tc.position());
//qDebug() << "KEY: " << key << text << "POS: " << m_tc.position();
if (m_mode == InsertMode)
return handleInsertMode(key, unmodified, text);
if (m_mode == CommandMode)
return handleCommandMode(key, unmodified, text);
if (m_mode == ExMode || m_mode == SearchForwardMode
|| m_mode == SearchBackwardMode)
return handleMiniBufferModes(key, unmodified, text);
return EventUnhandled;
}
void FakeVimHandler::Private::setUndoPosition(int pos)
{
//qDebug() << " CURSOR POS: " << m_undoCursorPosition;
m_undoCursorPosition[m_tc.document()->availableUndoSteps()] = pos;
}
void FakeVimHandler::Private::moveDown(int n)
{
#if 0
// does not work for "hidden" documents like in the autotests
m_tc.movePosition(Down, MoveAnchor, n);
#else
const int col = m_tc.position() - m_tc.block().position();
const int lastLine = m_tc.document()->lastBlock().blockNumber();
const int targetLine = qMax(0, qMin(lastLine, m_tc.block().blockNumber() + n));
const QTextBlock &block = m_tc.document()->findBlockByNumber(targetLine);
const int pos = block.position();
setPosition(pos + qMin(block.length() - 1, col));
moveToTargetColumn();
#endif
}
void FakeVimHandler::Private::moveToEndOfLine()
{
#if 0
// does not work for "hidden" documents like in the autotests
m_tc.movePosition(EndOfLine, MoveAnchor);
#else
const QTextBlock &block = m_tc.block();
setPosition(block.position() + block.length() - 1);
#endif
}
void FakeVimHandler::Private::moveBehindEndOfLine()
{
const QTextBlock &block = m_tc.block();
int pos = qMin(block.position() + block.length(), lastPositionInDocument());
setPosition(pos);
}
void FakeVimHandler::Private::moveToStartOfLine()
{
#if 0
// does not work for "hidden" documents like in the autotests
m_tc.movePosition(StartOfLine, MoveAnchor);
#else
const QTextBlock &block = m_tc.block();
setPosition(block.position());
#endif
}
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
{
//qDebug() << "ANCHOR: " << position() << anchor();
if (m_submode == FilterSubMode) {
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
setPosition(qMin(anchor(), position()));
enterExMode();
m_currentMessage.clear();
m_commandBuffer = QString(".,+%1!").arg(qAbs(endLine - beginLine));
m_commandHistory.append(QString());
m_commandHistoryIndex = m_commandHistory.size() - 1;
updateMiniBuffer();
return;
}
if (isNoVisualMode())
m_marks['>'] = m_tc.position();
if (m_submode == ChangeSubMode) {
if (m_movetype == MoveInclusive)
moveRight(); // correction
if (anchor() >= position())
m_anchor++;
if (!dotCommand.isEmpty())
setDotCommand("c" + dotCommand);
//QString text = removeSelectedText();
//qDebug() << "CHANGING TO INSERT MODE" << text;
//m_registers[m_register] = text;
yankSelectedText();
removeSelectedText();
enterInsertMode();
m_beginEditBlock = false;
m_submode = NoSubMode;
} else if (m_submode == DeleteSubMode) {
if (m_rangemode == RangeCharMode) {
if (m_movetype == MoveInclusive)
moveRight(); // correction
if (anchor() >= position())
m_anchor++;
}
if (!dotCommand.isEmpty())
setDotCommand("d" + dotCommand);
yankSelectedText();
removeSelectedText();
m_submode = NoSubMode;
if (atEndOfLine())
moveLeft();
else
setTargetColumn();
} else if (m_submode == YankSubMode) {
yankSelectedText();
m_submode = NoSubMode;
if (m_register != '"') {
setPosition(m_marks[m_register]);
moveToStartOfLine();
} else {
setPosition(m_savedYankPosition);
}
} else if (m_submode == ReplaceSubMode) {
m_submode = NoSubMode;
} else if (m_submode == IndentSubMode) {
recordJump();
indentRegion();
m_submode = NoSubMode;
updateMiniBuffer();
} else if (m_submode == ShiftRightSubMode) {
recordJump();
shiftRegionRight(1);
m_submode = NoSubMode;
updateMiniBuffer();
} else if (m_submode == ShiftLeftSubMode) {
recordJump();
shiftRegionLeft(1);
m_submode = NoSubMode;
updateMiniBuffer();
}
m_movetype = MoveInclusive;
m_mvcount.clear();
m_opcount.clear();
m_gflag = false;
m_register = '"';
m_tc.clearSelection();
m_rangemode = RangeCharMode;
updateSelection();
updateMiniBuffer();
}
void FakeVimHandler::Private::updateSelection()
{
QList<QTextEdit::ExtraSelection> selections = m_searchSelections;
if (isVisualMode()) {
QTextEdit::ExtraSelection sel;
sel.cursor = m_tc;
sel.format = m_tc.blockCharFormat();
#if 0
sel.format.setFontWeight(QFont::Bold);
sel.format.setFontUnderline(true);
#else
sel.format.setForeground(Qt::white);
sel.format.setBackground(Qt::black);
#endif
int cursorPos = m_tc.position();
int anchorPos = m_marks['<'];
//qDebug() << "POS: " << cursorPos << " ANCHOR: " << anchorPos;
if (isVisualCharMode()) {
sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
sel.cursor.setPosition(qMax(cursorPos, anchorPos) + 1, KeepAnchor);
selections.append(sel);
} else if (isVisualLineMode()) {
sel.cursor.setPosition(qMin(cursorPos, anchorPos), MoveAnchor);
sel.cursor.movePosition(StartOfLine, MoveAnchor);
sel.cursor.setPosition(qMax(cursorPos, anchorPos), KeepAnchor);
sel.cursor.movePosition(EndOfLine, KeepAnchor);
selections.append(sel);
} else if (isVisualBlockMode()) {
QTextCursor tc = m_tc;
tc.setPosition(anchorPos);
int anchorColumn = tc.columnNumber();
int cursorColumn = m_tc.columnNumber();
int anchorRow = tc.blockNumber();
int cursorRow = m_tc.blockNumber();
int startColumn = qMin(anchorColumn, cursorColumn);
int endColumn = qMax(anchorColumn, cursorColumn);
int diffRow = cursorRow - anchorRow;
if (anchorRow > cursorRow) {
tc.setPosition(cursorPos);
diffRow = -diffRow;
}
tc.movePosition(StartOfLine, MoveAnchor);
for (int i = 0; i <= diffRow; ++i) {
if (startColumn < tc.block().length() - 1) {
int last = qMin(tc.block().length(), endColumn + 1);
int len = last - startColumn;
sel.cursor = tc;
sel.cursor.movePosition(Right, MoveAnchor, startColumn);
sel.cursor.movePosition(Right, KeepAnchor, len);
selections.append(sel);
}
tc.movePosition(Down, MoveAnchor, 1);
}
}
}
//qDebug() << "SELECTION: " << selections;
emit q->selectionChanged(selections);
}
void FakeVimHandler::Private::updateMiniBuffer()
{
QString msg;
if (m_passing) {
msg = "-- PASSING -- ";
} else if (!m_currentMessage.isEmpty()) {
msg = m_currentMessage;
} else if (m_mode == CommandMode && isVisualMode()) {
if (isVisualCharMode()) {
msg = "-- VISUAL --";
} else if (isVisualLineMode()) {
msg = "-- VISUAL LINE --";
} else if (isVisualBlockMode()) {
msg = "-- VISUAL BLOCK --";
}
} else if (m_mode == InsertMode) {
if (m_submode == ReplaceSubMode)
msg = "-- REPLACE --";
else
msg = "-- INSERT --";
} else {
if (m_mode == SearchForwardMode)
msg += '/';
else if (m_mode == SearchBackwardMode)
msg += '?';
else if (m_mode == ExMode)
msg += ':';
foreach (QChar c, m_commandBuffer) {
if (c.unicode() < 32) {
msg += '^';
msg += QChar(c.unicode() + 64);
} else {
msg += c;
}
}
if (!msg.isEmpty() && m_mode != CommandMode)
msg += QChar(10073); // '|'; // FIXME: Use a real "cursor"
}
emit q->commandBufferChanged(msg);
int linesInDoc = linesInDocument();
int l = cursorLineInDocument();
QString status;
const QString pos = QString::fromLatin1("%1,%2").arg(l + 1).arg(cursorColumnInDocument() + 1);
// FIXME: physical "-" logical
if (linesInDoc != 0) {
status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
} else {
status = FakeVimHandler::tr("%1All").arg(pos, -10);
}
emit q->statusDataChanged(status);
}
void FakeVimHandler::Private::showRedMessage(const QString &msg)
{
//qDebug() << "MSG: " << msg;
m_currentMessage = msg;
updateMiniBuffer();
}
void FakeVimHandler::Private::showBlackMessage(const QString &msg)
{
//qDebug() << "MSG: " << msg;
m_commandBuffer = msg;
updateMiniBuffer();
}
void FakeVimHandler::Private::notImplementedYet()
{
qDebug() << "Not implemented in FakeVim";
showRedMessage(FakeVimHandler::tr("Not implemented in FakeVim"));
updateMiniBuffer();
}
EventResult FakeVimHandler::Private::handleCommandMode(int key, int unmodified,
const QString &text)
{
EventResult handled = EventHandled;
if (m_submode == WindowSubMode) {
emit q->windowCommandRequested(key);
m_submode = NoSubMode;
} else if (m_submode == RegisterSubMode) {
m_register = key;
m_submode = NoSubMode;
m_rangemode = RangeLineMode;
} else if (m_submode == ChangeSubMode && key == 'c') { // tested
moveDown(count() - 1);
moveToEndOfLine();
moveLeft();
setAnchor();
moveToStartOfLine();
setTargetColumn();
moveUp(count() - 1);
m_movetype = MoveLineWise;
m_lastInsertion.clear();
setDotCommand("%1cc", count());
finishMovement();
} else if (m_submode == DeleteSubMode && key == 'd') { // tested
m_movetype = MoveLineWise;
int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
Range range(position(), endPos, RangeLineMode);
yankText(range);
removeText(range);
setDotCommand("%1dd", count());
m_submode = NoSubMode;
moveToFirstNonBlankOnLine();
setTargetColumn();
finishMovement();
} else if (m_submode == ShiftLeftSubMode && key == '<') {
setAnchor();
moveDown(count() - 1);
m_movetype = MoveLineWise;
setDotCommand("%1<<", count());
finishMovement();
} else if (m_submode == ShiftRightSubMode && key == '>') {
setAnchor();
moveDown(count() - 1);
m_movetype = MoveLineWise;
setDotCommand("%1>>", count());
finishMovement();
} else if (m_submode == IndentSubMode && key == '=') {
setAnchor();
moveDown(count() - 1);
m_movetype = MoveLineWise;
setDotCommand("%1==", count());
finishMovement();
} else if (m_submode == ZSubMode) {
//qDebug() << "Z_MODE " << cursorLineInDocument() << linesOnScreen();
if (key == Key_Return || key == 't') { // cursor line to top of window
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(- cursorLineOnScreen());
if (key == Key_Return)
moveToFirstNonBlankOnLine();
finishMovement();
} else if (key == '.' || key == 'z') { // cursor line to center of window
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
if (key == '.')
moveToFirstNonBlankOnLine();
finishMovement();
} else if (key == '-' || key == 'b') { // cursor line to bottom of window
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
scrollUp(linesOnScreen() - cursorLineOnScreen());
if (key == '-')
moveToFirstNonBlankOnLine();
finishMovement();
} else {
qDebug() << "IGNORED Z_MODE " << key << text;
}
m_submode = NoSubMode;
} else if (m_submode == CapitalZSubMode) {
// Recognize ZZ and ZQ as aliases for ":x" and ":q!".
m_submode = NoSubMode;
if (key == 'Z')
handleCommand("x");
else if (key == 'Q')
handleCommand("q!");
} else if (m_subsubmode == FtSubSubMode) {
m_semicolonType = m_subsubdata;
m_semicolonKey = key;
handleFfTt(key);
m_subsubmode = NoSubSubMode;
finishMovement(QString("%1%2%3")
.arg(count())
.arg(QChar(m_semicolonType))
.arg(QChar(m_semicolonKey)));
} else if (m_submode == ReplaceSubMode) {
if (count() <= (rightDist() + atEndOfLine()) && text.size() == 1
&& (text.at(0).isPrint() || text.at(0).isSpace())) {
if (atEndOfLine())
moveLeft();
setAnchor();
moveRight(count());
removeSelectedText();
m_tc.insertText(QString(count(), text.at(0)));
m_movetype = MoveExclusive;
setDotCommand("%1r" + text, count());
}
setTargetColumn();
m_submode = NoSubMode;
finishMovement();
} 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)) {
setPosition(m_marks[key]);
if (m_subsubmode == TickSubSubMode)
moveToFirstNonBlankOnLine();
finishMovement();
} else {
showRedMessage(msgE20MarkNotSet(text));
}
m_subsubmode = NoSubSubMode;
} else if (key >= '0' && key <= '9') {
if (key == '0' && m_mvcount.isEmpty()) {
moveToStartOfLine();
setTargetColumn();
finishMovement();
} else {
m_mvcount.append(QChar(key));
}
} else if (key == '^') {
moveToFirstNonBlankOnLine();
finishMovement();
} 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();
} else if (key == ':') {
enterExMode();
m_currentMessage.clear();
m_commandBuffer.clear();
if (isVisualMode())
m_commandBuffer = "'<,'>";
m_commandHistory.append(QString());
m_commandHistoryIndex = m_commandHistory.size() - 1;
updateMiniBuffer();
} else if (key == '/' || key == '?') {
if (hasConfig(ConfigIncSearch)) {
// re-use the core dialog.
emit q->findRequested(key == '?');
} else {
// FIXME: make core find dialog sufficiently flexible to
// produce the "default vi" behaviour too. For now, roll our own.
enterExMode(); // to get the cursor disabled
m_currentMessage.clear();
m_mode = (key == '/') ? SearchForwardMode : SearchBackwardMode;
m_commandBuffer.clear();
m_searchHistory.append(QString());
m_searchHistoryIndex = m_searchHistory.size() - 1;
updateMiniBuffer();
}
} else if (key == '`') {
m_subsubmode = BackTickSubSubMode;
} 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);
recordJump();
} else if (key == '\'') {
m_subsubmode = TickSubSubMode;
} else if (key == '|') {
moveToStartOfLine();
moveRight(qMin(count(), rightDist()) - 1);
setTargetColumn();
finishMovement();
} else if (key == '!' && isNoVisualMode()) {
m_submode = FilterSubMode;
} else if (key == '!' && isVisualMode()) {
enterExMode();
m_currentMessage.clear();
m_commandBuffer = "'<,'>!";
m_commandHistory.append(QString());
m_commandHistoryIndex = m_commandHistory.size() - 1;
updateMiniBuffer();
} else if (key == '"') {
m_submode = RegisterSubMode;
} else if (unmodified == Key_Return) {
moveToStartOfLine();
moveDown();
moveToFirstNonBlankOnLine();
finishMovement();
} else if (key == '-') {
moveToStartOfLine();
moveUp();
moveToFirstNonBlankOnLine();
finishMovement();
} else if (key == Key_Home) {
moveToStartOfLine();
setTargetColumn();
finishMovement();
} else if (key == '$' || key == Key_End) {
int submode = m_submode;
moveToEndOfLine();
m_movetype = MoveExclusive;
setTargetColumn();
if (submode == NoSubMode)
m_targetColumn = -1;
finishMovement("$");
} else if (key == ',') {
// FIXME: use some other mechanism
//m_passing = true;
m_passing = !m_passing;
updateMiniBuffer();
} else if (key == '.') {
//qDebug() << "REPEATING" << quoteUnprintable(m_dotCommand) << count();
QString savedCommand = m_dotCommand;
m_dotCommand.clear();
replay(savedCommand, count());
enterCommandMode();
m_dotCommand = savedCommand;
} else if (key == '<' && isNoVisualMode()) {
m_submode = ShiftLeftSubMode;
} else if (key == '<' && isVisualMode()) {
shiftRegionLeft(1);
leaveVisualMode();
} else if (key == '>' && isNoVisualMode()) {
m_submode = ShiftRightSubMode;
} else if (key == '>' && isVisualMode()) {
shiftRegionRight(1);
leaveVisualMode();
} else if (key == '=' && isNoVisualMode()) {
m_submode = IndentSubMode;
} else if (key == '=' && isVisualMode()) {
indentRegion();
leaveVisualMode();
} else if (key == '%') {
m_movetype = MoveExclusive;
moveToMatchingParanthesis();
finishMovement();
} else if (key == 'a') {
enterInsertMode();
m_lastInsertion.clear();
if (!atEndOfLine())
moveRight();
updateMiniBuffer();
} else if (key == 'A') {
enterInsertMode();
moveToEndOfLine();
setDotCommand("A");
m_lastInsertion.clear();
updateMiniBuffer();
} else if (key == control('a')) {
// FIXME: eat it to prevent the global "select all" shortcut to trigger
} else if (key == 'b') {
m_movetype = MoveExclusive;
moveToWordBoundary(false, false);
finishMovement();
} else if (key == 'B') {
m_movetype = MoveExclusive;
moveToWordBoundary(true, false);
finishMovement();
} else if (key == 'c' && isNoVisualMode()) {
setAnchor();
m_submode = ChangeSubMode;
} else if (key == 'c' && isVisualCharMode()) {
leaveVisualMode();
m_submode = ChangeSubMode;
finishMovement();
} else if (key == 'C') {
setAnchor();
moveToEndOfLine();
yankSelectedText();
removeSelectedText();
enterInsertMode();
m_beginEditBlock = false;
setDotCommand("C");
finishMovement();
} else if (key == control('c')) {
if (isNoVisualMode())
showBlackMessage("Type Alt-v,Alt-v to quit FakeVim mode");
else
leaveVisualMode();
} else if (key == 'd' && isNoVisualMode()) {
if (m_rangemode == RangeLineMode) {
m_savedYankPosition = m_tc.position();
moveToEndOfLine();
setAnchor();
setPosition(m_savedYankPosition);
} else {
if (atEndOfLine())
moveLeft();
setAnchor();
}
m_opcount = m_mvcount;
m_mvcount.clear();
m_submode = DeleteSubMode;
} else if ((key == 'd' || key == 'x') && isVisualCharMode()) {
leaveVisualMode();
m_submode = DeleteSubMode;
finishMovement();
} else if ((key == 'd' || key == 'x') && isVisualLineMode()) {
leaveVisualMode();
m_rangemode = RangeLineMode;
yankSelectedText();
removeSelectedText();
moveToFirstNonBlankOnLine();
} else if ((key == 'd' || key == 'x') && isVisualBlockMode()) {
leaveVisualMode();
m_rangemode = RangeBlockMode;
yankSelectedText();
removeSelectedText();
setPosition(qMin(position(), anchor()));
} else if (key == 'D' && isNoVisualMode()) {
setAnchor();
m_submode = DeleteSubMode;
moveDown(qMax(count() - 1, 0));
m_movetype = MoveExclusive;
moveToEndOfLine();
setDotCommand("D");
finishMovement();
} else if ((key == 'D' || key == 'X') &&
(isVisualCharMode() || isVisualLineMode())) {
leaveVisualMode();
m_rangemode = RangeLineMode;
m_submode = NoSubMode;
yankSelectedText();
removeSelectedText();
moveToFirstNonBlankOnLine();
} else if ((key == 'D' || key == 'X') && isVisualBlockMode()) {
leaveVisualMode();
m_rangemode = RangeBlockAndTailMode;
yankSelectedText();
removeSelectedText();
setPosition(qMin(position(), anchor()));
} else if (key == control('d')) {
int sline = cursorLineOnScreen();
// FIXME: this should use the "scroll" option, and "count"
moveDown(linesOnScreen() / 2);
handleStartOfLine();
scrollToLineInDocument(cursorLineInDocument() - sline);
finishMovement();
} else if (key == 'e') { // tested
m_movetype = MoveInclusive;
moveToWordBoundary(false, true);
finishMovement("e");
} else if (key == 'E') {
m_movetype = MoveInclusive;
moveToWordBoundary(true, true);
finishMovement();
} else if (key == control('e')) {
// FIXME: this should use the "scroll" option, and "count"
if (cursorLineOnScreen() == 0)
moveDown(1);
scrollDown(1);
finishMovement();
} else if (key == 'f') {
m_subsubmode = FtSubSubMode;
m_movetype = MoveInclusive;
m_subsubdata = key;
} else if (key == 'F') {
m_subsubmode = FtSubSubMode;
m_movetype = MoveExclusive;
m_subsubdata = key;
} else if (key == 'g') {
if (m_gflag) {
m_gflag = false;
m_tc.setPosition(firstPositionInLine(1), KeepAnchor);
handleStartOfLine();
finishMovement();
} else {
m_gflag = true;
}
} else if (key == 'G') {
int n = m_mvcount.isEmpty() ? linesInDocument() : count();
m_tc.setPosition(firstPositionInLine(n), KeepAnchor);
handleStartOfLine();
finishMovement();
} else if (key == 'h' || key == Key_Left
|| key == Key_Backspace || key == control('h')) {
int n = qMin(count(), leftDist());
if (m_fakeEnd && m_tc.block().length() > 1)
++n;
moveLeft(n);
setTargetColumn();
finishMovement("h");
} else if (key == 'H') {
m_tc = EDITOR(cursorForPosition(QPoint(0, 0)));
moveDown(qMax(count() - 1, 0));
handleStartOfLine();
finishMovement();
} else if (key == 'i' || key == Key_Insert) {
setDotCommand("i"); // setDotCommand("%1i", count());
enterInsertMode();
updateMiniBuffer();
if (atEndOfLine())
moveLeft();
} else if (key == 'I') {
setDotCommand("I"); // setDotCommand("%1I", count());
enterInsertMode();
if (m_gflag)
moveToStartOfLine();
else
moveToFirstNonBlankOnLine();
m_tc.clearSelection();
} else if (key == control('i')) {
if (!m_jumpListRedo.isEmpty()) {
m_jumpListUndo.append(cursorPosition());
setCursorPosition(m_jumpListRedo.last());
m_jumpListRedo.pop_back();
}
} else if (key == 'j' || key == Key_Down) {
if (m_submode == NoSubMode || m_submode == ZSubMode
|| m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
moveDown(count());
} else {
m_movetype = MoveLineWise;
moveToStartOfLine();
setAnchor();
moveDown(count() + 1);
}
finishMovement("j");
} else if (key == 'J') {
setDotCommand("%1J", count());
beginEditBlock();
if (m_submode == NoSubMode) {
for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
moveToEndOfLine();
setAnchor();
moveRight();
if (m_gflag) {
removeSelectedText();
} else {
while (characterAtCursor() == ' '
|| characterAtCursor() == '\t')
moveRight();
removeSelectedText();
m_tc.insertText(" ");
}
}
if (!m_gflag)
moveLeft();
}
endEditBlock();
finishMovement();
} else if (key == 'k' || key == Key_Up) {
if (m_submode == NoSubMode || m_submode == ZSubMode
|| m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
moveUp(count());
} else {
m_movetype = MoveLineWise;
moveToStartOfLine();
moveDown();
setAnchor();
moveUp(count() + 1);
}
finishMovement("k");
} else if (key == 'l' || key == Key_Right || key == ' ') {
m_movetype = MoveExclusive;
moveRight(qMin(count(), rightDist()));
setTargetColumn();
finishMovement("l");
} else if (key == 'L') {
m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
moveUp(qMax(count(), 1));
handleStartOfLine();
finishMovement();
} else if (key == control('l')) {
// screen redraw. should not be needed
} else if (key == 'm') {
m_subsubmode = MarkSubSubMode;
} else if (key == 'M') {
m_tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
handleStartOfLine();
finishMovement();
} else if (key == 'n') { // FIXME: see comment for '/'
if (hasConfig(ConfigIncSearch))
emit q->findNextRequested(false);
else
search(lastSearchString(), m_lastSearchForward);
recordJump();
} else if (key == 'N') {
if (hasConfig(ConfigIncSearch))
emit q->findNextRequested(true);
else
search(lastSearchString(), !m_lastSearchForward);
recordJump();
} else if (key == 'o' || key == 'O') {
beginEditBlock();
setDotCommand("%1o", count());
enterInsertMode();
m_beginEditBlock = false;
moveToFirstNonBlankOnLine();
if (key == 'O')
moveToStartOfLine();
else
moveToEndOfLine();
m_tc.insertText("\n");
if (key == 'O')
moveUp();
insertAutomaticIndentation(key == 'o');
endEditBlock();
} else if (key == control('o')) {
if (!m_jumpListUndo.isEmpty()) {
m_jumpListRedo.append(cursorPosition());
setCursorPosition(m_jumpListUndo.last());
m_jumpListUndo.pop_back();
}
} else if (key == 'p' || key == 'P') {
pasteText(key == 'p');
setTargetColumn();
setDotCommand("%1p", count());
finishMovement();
} else if (key == 'r') {
m_submode = ReplaceSubMode;
setDotCommand("r");
} else if (key == 'R') {
// FIXME: right now we repeat the insertion count() times,
// but not the deletion
m_lastInsertion.clear();
enterInsertMode();
m_submode = ReplaceSubMode;
setDotCommand("R");
} else if (key == control('r')) {
redo();
} else if (key == 's') {
if (atEndOfLine())
moveLeft();
setAnchor();
moveRight(qMin(count(), rightDist()));
yankSelectedText();
removeSelectedText();
setDotCommand("%1s", count());
m_opcount.clear();
m_mvcount.clear();
enterInsertMode();
} else if (key == 'S') {
const int line = cursorLineInDocument() + 1;
setAnchor(firstPositionInLine(line));
setPosition(lastPositionInLine(line + count() - 1));
yankSelectedText();
removeSelectedText();
setDotCommand("%1S", count());
m_opcount.clear();
m_mvcount.clear();
enterInsertMode();
m_beginEditBlock = false;
} else if (key == 't') {
m_movetype = MoveInclusive;
m_subsubmode = FtSubSubMode;
m_subsubdata = key;
} else if (key == 'T') {
m_movetype = MoveExclusive;
m_subsubmode = FtSubSubMode;
m_subsubdata = key;
} else if (key == 'u') {
undo();
} else if (key == control('u')) {
int sline = cursorLineOnScreen();
// FIXME: this should use the "scroll" option, and "count"
moveUp(linesOnScreen() / 2);
handleStartOfLine();
scrollToLineInDocument(cursorLineInDocument() - sline);
finishMovement();
} else if (key == 'v') {
enterVisualMode(VisualCharMode);
} else if (key == 'V') {
enterVisualMode(VisualLineMode);
} else if (key == control('v')) {
enterVisualMode(VisualBlockMode);
} else if (key == 'w') { // tested
// Special case: "cw" and "cW" work the same as "ce" and "cE" if the
// cursor is on a non-blank.
if (m_submode == ChangeSubMode) {
moveToWordBoundary(false, true);
m_movetype = MoveInclusive;
} else {
moveToNextWord(false);
m_movetype = MoveExclusive;
}
finishMovement("w");
} else if (key == 'W') {
if (m_submode == ChangeSubMode) {
moveToWordBoundary(true, true);
m_movetype = MoveInclusive;
} else {
moveToNextWord(true);
m_movetype = MoveExclusive;
}
finishMovement("W");
} else if (key == control('w')) {
m_submode = WindowSubMode;
} else if (key == 'x' && isNoVisualMode()) { // = "dl"
m_movetype = MoveExclusive;
if (atEndOfLine())
moveLeft();
setAnchor();
m_submode = DeleteSubMode;
moveRight(qMin(count(), rightDist()));
setDotCommand("%1x", count());
finishMovement();
} else if (key == 'X') {
if (leftDist() > 0) {
setAnchor();
moveLeft(qMin(count(), leftDist()));
yankSelectedText();
removeSelectedText();
}
finishMovement();
} else if ((m_submode == YankSubMode && key == 'y')
|| (key == 'Y' && isNoVisualMode())) {
const int line = cursorLineInDocument() + 1;
m_savedYankPosition = position();
setAnchor(firstPositionInLine(line));
setPosition(lastPositionInLine(line + count() - 1));
if (count() > 1)
showBlackMessage(QString("%1 lines yanked").arg(count()));
m_rangemode = RangeLineMode;
m_movetype = MoveLineWise;
m_submode = YankSubMode;
finishMovement();
} else if (key == 'y' && isNoVisualMode()) {
if (m_rangemode == RangeLineMode) {
m_savedYankPosition = position();
setAnchor(firstPositionInLine(cursorLineInDocument() + 1));
} else {
m_savedYankPosition = position();
if (atEndOfLine())
moveLeft();
setAnchor();
m_rangemode = RangeCharMode;
}
m_submode = YankSubMode;
} else if (key == 'y' && isVisualCharMode()) {
Range range(position(), anchor(), RangeCharMode);
range.endPos++; // MoveInclusive
yankText(range, m_register);
setPosition(qMin(position(), anchor()));
leaveVisualMode();
finishMovement();
} else if ((key == 'y' && isVisualLineMode())
|| (key == 'Y' && isVisualLineMode())
|| (key == 'Y' && isVisualCharMode())) {
m_rangemode = RangeLineMode;
yankSelectedText();
setPosition(qMin(position(), anchor()));
moveToStartOfLine();
leaveVisualMode();
finishMovement();
} else if ((key == 'y' || key == 'Y') && isVisualBlockMode()) {
m_rangemode = RangeBlockMode;
yankSelectedText();
setPosition(qMin(position(), anchor()));
leaveVisualMode();
finishMovement();
} else if (key == 'z') {
m_submode = ZSubMode;
} else if (key == 'Z') {
m_submode = CapitalZSubMode;
} else if (key == '~' && !atEndOfLine()) {
beginEditBlock();
setAnchor();
moveRight(qMin(count(), rightDist()));
QString str = selectedText();
removeSelectedText();
for (int i = str.size(); --i >= 0; ) {
QChar c = str.at(i);
str[i] = c.isUpper() ? c.toLower() : c.toUpper();
}
m_tc.insertText(str);
endEditBlock();
} else if (key == Key_PageDown || key == control('f')) {
moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
scrollToLineInDocument(cursorLineInDocument());
handleStartOfLine();
finishMovement();
} else if (key == Key_PageUp || key == control('b')) {
moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
scrollToLineInDocument(cursorLineInDocument() + linesOnScreen() - 2);
handleStartOfLine();
finishMovement();
} else if (key == Key_Delete) {
setAnchor();
moveRight(qMin(1, rightDist()));
removeSelectedText();
} else if (key == Key_BracketLeft || key == Key_BracketRight) {
} else if (key == Key_Escape) {
if (isVisualMode()) {
leaveVisualMode();
} else if (m_submode != NoSubMode) {
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
finishMovement();
}
} else {
qDebug() << "IGNORED IN COMMAND MODE: " << key << text
<< " VISUAL: " << m_visualMode;
handled = EventUnhandled;
}
return handled;
}
EventResult FakeVimHandler::Private::handleInsertMode(int key, int,
const QString &text)
{
if (key == Key_Escape || key == 27 || key == control('c') ||
key == 379 /* ^[ */) {
// start with '1', as one instance was already physically inserted
// while typing
QString data = m_lastInsertion;
for (int i = 1; i < count(); ++i) {
m_tc.insertText(m_lastInsertion);
data += m_lastInsertion;
}
moveLeft(qMin(1, leftDist()));
setTargetColumn();
m_dotCommand += m_lastInsertion;
m_dotCommand += QChar(27);
recordNewUndo();
enterCommandMode();
} else if (key == Key_Insert) {
if (m_submode == ReplaceSubMode) {
EDITOR(setCursorWidth(m_cursorWidth));
EDITOR(setOverwriteMode(false));
m_submode = NoSubMode;
} else {
EDITOR(setCursorWidth(m_cursorWidth));
EDITOR(setOverwriteMode(true));
m_submode = ReplaceSubMode;
}
} else if (key == Key_Left) {
moveLeft(count());
setTargetColumn();
m_lastInsertion.clear();
} else if (key == Key_Down) {
//removeAutomaticIndentation();
m_submode = NoSubMode;
moveDown(count());
m_lastInsertion.clear();
} else if (key == Key_Up) {
//removeAutomaticIndentation();
m_submode = NoSubMode;
moveUp(count());
m_lastInsertion.clear();
} else if (key == Key_Right) {
moveRight(count());
setTargetColumn();
m_lastInsertion.clear();
} else if (key == Key_Return) {
m_submode = NoSubMode;
m_tc.insertBlock();
m_lastInsertion += "\n";
insertAutomaticIndentation(true);
setTargetColumn();
} else if (key == Key_Backspace || key == control('h')) {
joinPreviousEditBlock();
if (!removeAutomaticIndentation()
&& (!m_lastInsertion.isEmpty()
|| hasConfig(ConfigBackspace, "start")))
{
int line = cursorLineInDocument() + 1;
int col = cursorColumnInDocument();
QString data = lineContents(line);
Indentation ind = indentation(data);
if (col <= ind.logical) {
int ts = config(ConfigTabStop).toInt();
int newcol = col - 1 - (col - 1) % ts;
data = tabExpand(newcol) + data.mid(col);
setLineContents(line, data);
m_lastInsertion.clear(); // FIXME
} else {
m_tc.deletePreviousChar();
m_lastInsertion.chop(1);
}
setTargetColumn();
}
endEditBlock();
} else if (key == Key_Delete) {
m_tc.deleteChar();
m_lastInsertion.clear();
} else if (key == Key_PageDown || key == control('f')) {
removeAutomaticIndentation();
moveDown(count() * (linesOnScreen() - 2));
m_lastInsertion.clear();
} else if (key == Key_PageUp || key == control('b')) {
removeAutomaticIndentation();
moveUp(count() * (linesOnScreen() - 2));
m_lastInsertion.clear();
} else if (key == Key_Tab && hasConfig(ConfigExpandTab)) {
int ts = config(ConfigTabStop).toInt();
int col = cursorColumnInDocument();
QString str = QString(ts - col % ts, ' ');
m_lastInsertion.append(str);
m_tc.insertText(str);
setTargetColumn();
} else if (key >= control('a') && key <= control('z')) {
// ignore these
} else if (!text.isEmpty()) {
if (m_beginEditBlock)
{
beginEditBlock();
m_beginEditBlock = false;
}
else
joinPreviousEditBlock();
m_justAutoIndented = false;
m_lastInsertion.append(text);
if (m_submode == ReplaceSubMode) {
if (atEndOfLine())
m_submode = NoSubMode;
else
m_tc.deleteChar();
}
m_tc.insertText(text);
if (0 && hasConfig(ConfigAutoIndent) && isElectricCharacter(text.at(0))) {
const QString leftText = m_tc.block().text()
.left(m_tc.position() - 1 - m_tc.block().position());
if (leftText.simplified().isEmpty())
indentRegion(text.at(0));
}
if (!m_inReplay)
emit q->completionRequested();
setTargetColumn();
endEditBlock();
} else {
return EventUnhandled;
}
updateMiniBuffer();
return EventHandled;
}
EventResult FakeVimHandler::Private::handleMiniBufferModes(int key, int unmodified,
const QString &text)
{
Q_UNUSED(text)
if (key == Key_Escape || key == control('c')) {
m_commandBuffer.clear();
enterCommandMode();
updateMiniBuffer();
} else if (key == Key_Backspace) {
if (m_commandBuffer.isEmpty()) {
enterCommandMode();
} else {
m_commandBuffer.chop(1);
}
updateMiniBuffer();
} else if (key == Key_Left) {
// FIXME:
if (!m_commandBuffer.isEmpty())
m_commandBuffer.chop(1);
updateMiniBuffer();
} else if (unmodified == Key_Return && m_mode == ExMode) {
if (!m_commandBuffer.isEmpty()) {
m_commandHistory.takeLast();
m_commandHistory.append(m_commandBuffer);
handleExCommand(m_commandBuffer);
leaveVisualMode();
}
} else if (unmodified == Key_Return && isSearchMode()) {
if (!m_commandBuffer.isEmpty()) {
m_searchHistory.takeLast();
m_searchHistory.append(m_commandBuffer);
m_lastSearchForward = (m_mode == SearchForwardMode);
search(lastSearchString(), m_lastSearchForward);
recordJump();
}
enterCommandMode();
updateMiniBuffer();
} else if ((key == Key_Up || key == Key_PageUp) && isSearchMode()) {
// FIXME: This and the three cases below are wrong as vim
// takes only matching entries in the history into account.
if (m_searchHistoryIndex > 0) {
--m_searchHistoryIndex;
showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
}
} else if ((key == Key_Up || key == Key_PageUp) && m_mode == ExMode) {
if (m_commandHistoryIndex > 0) {
--m_commandHistoryIndex;
showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
}
} else if ((key == Key_Down || key == Key_PageDown) && isSearchMode()) {
if (m_searchHistoryIndex < m_searchHistory.size() - 1) {
++m_searchHistoryIndex;
showBlackMessage(m_searchHistory.at(m_searchHistoryIndex));
}
} else if ((key == Key_Down || key == Key_PageDown) && m_mode == ExMode) {
if (m_commandHistoryIndex < m_commandHistory.size() - 1) {
++m_commandHistoryIndex;
showBlackMessage(m_commandHistory.at(m_commandHistoryIndex));
}
} else if (key == Key_Tab) {
m_commandBuffer += QChar(9);
updateMiniBuffer();
} else if (QChar(key).isPrint()) {
m_commandBuffer += QChar(key);
updateMiniBuffer();
} else {
qDebug() << "IGNORED IN MINIBUFFER MODE: " << key << text;
return EventUnhandled;
}
return EventHandled;
}
// 1 based.
int FakeVimHandler::Private::readLineCode(QString &cmd)
{
//qDebug() << "CMD: " << cmd;
if (cmd.isEmpty())
return -1;
QChar c = cmd.at(0);
cmd = cmd.mid(1);
if (c == '.')
return cursorLineInDocument() + 1;
if (c == '$')
return linesInDocument();
if (c == '\'' && !cmd.isEmpty()) {
int mark = m_marks.value(cmd.at(0).unicode());
if (!mark) {
showRedMessage(msgE20MarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
return -1;
}
cmd = cmd.mid(1);
return lineForPosition(mark);
}
if (c == '-') {
int n = readLineCode(cmd);
return cursorLineInDocument() + 1 - (n == -1 ? 1 : n);
}
if (c == '+') {
int n = readLineCode(cmd);
return cursorLineInDocument() + 1 + (n == -1 ? 1 : n);
}
if (c == '\'' && !cmd.isEmpty()) {
int pos = m_marks.value(cmd.at(0).unicode(), -1);
//qDebug() << " MARK: " << cmd.at(0) << pos << lineForPosition(pos);
if (pos == -1) {
showRedMessage(msgE20MarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
return -1;
}
cmd = cmd.mid(1);
return lineForPosition(pos);
}
if (c.isDigit()) {
int n = c.unicode() - '0';
while (!cmd.isEmpty()) {
c = cmd.at(0);
if (!c.isDigit())
break;
cmd = cmd.mid(1);
n = n * 10 + (c.unicode() - '0');
}
//qDebug() << "N: " << n;
return n;
}
// not parsed
cmd = c + cmd;
return -1;
}
void FakeVimHandler::Private::selectRange(int beginLine, int endLine)
{
if (beginLine == -1)
beginLine = cursorLineInDocument();
if (endLine == -1)
endLine = cursorLineInDocument();
if (beginLine > endLine)
qSwap(beginLine, endLine);
setAnchor(firstPositionInLine(beginLine));
if (endLine == linesInDocument())
setPosition(lastPositionInLine(endLine));
else
setPosition(firstPositionInLine(endLine + 1));
}
void FakeVimHandler::Private::handleCommand(const QString &cmd)
{
m_tc = EDITOR(textCursor());
handleExCommand(cmd);
EDITOR(setTextCursor(m_tc));
}
// result: (needle, replacement, opions)
static bool isSubstitution(const QString &cmd0, QStringList *result)
{
QString cmd;
if (cmd0.startsWith("substitute"))
cmd = cmd0.mid(10);
else if (cmd0.startsWith('s') && cmd0.size() > 1
&& !isalpha(cmd0.at(1).unicode()))
cmd = cmd0.mid(1);
else
return false;
// we have /{pattern}/{string}/[flags] now
if (cmd.isEmpty())
return false;
const QChar separator = cmd.at(0);
int pos1 = -1;
int pos2 = -1;
int i;
for (i = 1; i < cmd.size(); ++i) {
if (cmd.at(i) == separator && cmd.at(i - 1) != '\\') {
pos1 = i;
break;
}
}
if (pos1 == -1)
return false;
for (++i; i < cmd.size(); ++i) {
if (cmd.at(i) == separator && cmd.at(i - 1) != '\\') {
pos2 = i;
break;
}
}
if (pos2 == -1)
pos2 = cmd.size();
result->append(cmd.mid(1, pos1 - 1));
result->append(cmd.mid(pos1 + 1, pos2 - pos1 - 1));
result->append(cmd.mid(pos2 + 1));
return true;
}
void FakeVimHandler::Private::handleExCommand(const QString &cmd0)
{
QString cmd = cmd0;
// FIXME: that seems to be different for %w and %s
if (cmd.startsWith(QLatin1Char('%')))
cmd = "1,$" + cmd.mid(1);
int beginLine = -1;
int endLine = -1;
int line = readLineCode(cmd);
if (line != -1)
beginLine = line;
if (cmd.startsWith(',')) {
cmd = cmd.mid(1);
line = readLineCode(cmd);
if (line != -1)
endLine = line;
}
//qDebug() << "RANGE: " << beginLine << endLine << cmd << cmd0 << m_marks;
static QRegExp reQuit("^qa?!?$");
static QRegExp reDelete("^d( (.*))?$");
static QRegExp reHistory("^his(tory)?( (.*))?$");
static QRegExp reNormal("^norm(al)?( (.*))?$");
static QRegExp reSet("^set?( (.*))?$");
static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
QStringList arguments;
enterCommandMode();
showBlackMessage(QString());
if (cmd.isEmpty()) {
setPosition(firstPositionInLine(beginLine));
showBlackMessage(QString());
} else if (reDelete.indexIn(cmd) != -1) { // :d
selectRange(beginLine, endLine);
QString reg = reDelete.cap(2);
QString text = selectedText();
removeSelectedText();
if (!reg.isEmpty()) {
Register &r = m_registers[reg.at(0).unicode()];
r.contents = text;
r.rangemode = RangeLineMode;
}
} else if (reWrite.indexIn(cmd) != -1) { // :w and :x
bool noArgs = (beginLine == -1);
if (beginLine == -1)
beginLine = 0;
if (endLine == -1)
endLine = linesInDocument();
//qDebug() << "LINES: " << beginLine << endLine;
int indexOfSpace = cmd.indexOf(QChar(' '));
QString prefix;
if (indexOfSpace < 0)
prefix = cmd;
else
prefix = cmd.left(indexOfSpace);
bool forced = prefix.contains(QChar('!'));
bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x'));
bool quitAll = quit && prefix.contains(QChar('a'));
QString fileName = reWrite.cap(2);
if (fileName.isEmpty())
fileName = m_currentFileName;
QFile file1(fileName);
bool exists = file1.exists();
if (exists && !forced && !noArgs) {
showRedMessage(FakeVimHandler::tr
("File '%1' exists (add ! to override)").arg(fileName));
} else if (file1.open(QIODevice::ReadWrite)) {
file1.close();
QTextCursor tc = m_tc;
Range range(firstPositionInLine(beginLine),
firstPositionInLine(endLine), RangeLineMode);
QString contents = text(range);
m_tc = tc;
//qDebug() << "LINES: " << beginLine << endLine;
bool handled = false;
emit q->writeFileRequested(&handled, fileName, contents);
// nobody cared, so act ourselves
if (!handled) {
//qDebug() << "HANDLING MANUAL SAVE TO " << fileName;
QFile::remove(fileName);
QFile file2(fileName);
if (file2.open(QIODevice::ReadWrite)) {
QTextStream ts(&file2);
ts << contents;
} else {
showRedMessage(FakeVimHandler::tr
("Cannot open file '%1' for writing").arg(fileName));
}
}
// check result by reading back
QFile file3(fileName);
file3.open(QIODevice::ReadOnly);
QByteArray ba = file3.readAll();
showBlackMessage(FakeVimHandler::tr("\"%1\" %2 %3L, %4C written")
.arg(fileName).arg(exists ? " " : " [New] ")
.arg(ba.count('\n')).arg(ba.size()));
if (quitAll)
passUnknownExCommand(forced ? "qa!" : "qa");
else if (quit)
passUnknownExCommand(forced ? "q!" : "q");
} else {
showRedMessage(FakeVimHandler::tr
("Cannot open file '%1' for reading").arg(fileName));
}
} else if (cmd.startsWith(QLatin1String("r "))) { // :r
m_currentFileName = cmd.mid(2);
QFile file(m_currentFileName);
file.open(QIODevice::ReadOnly);
QTextStream ts(&file);
QString data = ts.readAll();
EDITOR(setPlainText(data));
showBlackMessage(FakeVimHandler::tr("\"%1\" %2L, %3C")
.arg(m_currentFileName).arg(data.count('\n')).arg(data.size()));
} else if (cmd.startsWith(QLatin1Char('!'))) {
selectRange(beginLine, endLine);
QString command = cmd.mid(1).trimmed();
QString text = selectedText();
removeSelectedText();
QProcess proc;
proc.start(cmd.mid(1));
proc.waitForStarted();
proc.write(text.toUtf8());
proc.closeWriteChannel();
proc.waitForFinished();
QString result = QString::fromUtf8(proc.readAllStandardOutput());
m_tc.insertText(result);
leaveVisualMode();
setPosition(firstPositionInLine(beginLine));
//qDebug() << "FILTER: " << command;
showBlackMessage(FakeVimHandler::tr("%n lines filtered", 0,
text.count('\n')));
} else if (cmd.startsWith(QLatin1Char('>'))) {
m_anchor = firstPositionInLine(beginLine);
setPosition(firstPositionInLine(endLine));
shiftRegionRight(1);
leaveVisualMode();
showBlackMessage(FakeVimHandler::tr("%n lines >ed %1 time", 0,
(endLine - beginLine + 1)).arg(1));
} else if (cmd == "red" || cmd == "redo") { // :redo
redo();
updateMiniBuffer();
} else if (reNormal.indexIn(cmd) != -1) { // :normal
//qDebug() << "REPLAY: " << reNormal.cap(3);
replay(reNormal.cap(3), 1);
} else if (isSubstitution(cmd, &arguments)) { // :substitute
QString needle = arguments.at(0);
const QString replacement = arguments.at(1);
QString flags = arguments.at(2);
needle.replace('$', '\n');
needle.replace("\\\n", "\\$");
QRegExp pattern(needle);
if (flags.contains('i'))
pattern.setCaseSensitivity(Qt::CaseInsensitive);
const bool global = flags.contains('g');
beginEditBlock();
for (int line = endLine; line >= beginLine; --line) {
QString origText = lineContents(line);
QString text = origText;
int pos = 0;
while (true) {
pos = pattern.indexIn(text, pos, QRegExp::CaretAtZero);
if (pos == -1)
break;
if (pattern.cap(0).isEmpty())
break;
QStringList caps = pattern.capturedTexts();
QString matched = text.mid(pos, caps.at(0).size());
QString repl = replacement;
for (int i = 1; i < caps.size(); ++i)
repl.replace("\\" + QString::number(i), caps.at(i));
for (int i = 0; i < repl.size(); ++i) {
if (repl.at(i) == '&' && (i == 0 || repl.at(i - 1) != '\\')) {
repl.replace(i, 1, caps.at(0));
i += caps.at(0).size();
}
}
text = text.left(pos) + repl + text.mid(pos + matched.size());
pos += matched.size();
if (!global)
break;
}
if (text != origText)
setLineContents(line, text);
}
endEditBlock();
} else if (reSet.indexIn(cmd) != -1) { // :set
showBlackMessage(QString());
QString arg = reSet.cap(2);
SavedAction *act = theFakeVimSettings()->item(arg);
if (arg.isEmpty()) {
theFakeVimSetting(SettingsDialog)->trigger(QVariant());
} else if (act && act->value().type() == QVariant::Bool) {
// boolean config to be switched on
bool oldValue = act->value().toBool();
if (oldValue == false)
act->setValue(true);
else if (oldValue == true)
{} // nothing to do
} else if (act) {
// non-boolean to show
showBlackMessage(arg + '=' + act->value().toString());
} else if (arg.startsWith("no")
&& (act = theFakeVimSettings()->item(arg.mid(2)))) {
// boolean config to be switched off
bool oldValue = act->value().toBool();
if (oldValue == true)
act->setValue(false);
else if (oldValue == false)
{} // nothing to do
} else if (arg.contains('=')) {
// non-boolean config to set
int p = arg.indexOf('=');
act = theFakeVimSettings()->item(arg.left(p));
if (act)
act->setValue(arg.mid(p + 1));
} else {
showRedMessage(FakeVimHandler::tr("E512: Unknown option: ") + arg);
}
updateMiniBuffer();
} else if (reHistory.indexIn(cmd) != -1) { // :history
QString arg = reSet.cap(3);
if (arg.isEmpty()) {
QString info;
info += "# command history\n";
int i = 0;
foreach (const QString &item, m_commandHistory) {
++i;
info += QString("%1 %2\n").arg(i, -8).arg(item);
}
emit q->extraInformationChanged(info);
} else {
notImplementedYet();
}
updateMiniBuffer();
} else {
passUnknownExCommand(cmd);
}
}
void FakeVimHandler::Private::passUnknownExCommand(const QString &cmd)
{
emit q->handleExCommandRequested(cmd);
}
static void vimPatternToQtPattern(QString *needle, QTextDocument::FindFlags *flags)
{
// FIXME: Rough mapping of a common case
if (needle->startsWith("\\<") && needle->endsWith("\\>"))
(*flags) |= QTextDocument::FindWholeWords;
needle->replace("\\<", ""); // start of word
needle->replace("\\>", ""); // end of word
//qDebug() << "NEEDLE " << needle0 << needle;
}
void FakeVimHandler::Private::search(const QString &needle0, bool forward)
{
showBlackMessage((forward ? '/' : '?') + needle0);
CursorPosition origPosition = cursorPosition();
QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
if (!forward)
flags |= QTextDocument::FindBackward;
QString needle = needle0;
vimPatternToQtPattern(&needle, &flags);
if (forward)
m_tc.movePosition(Right, MoveAnchor, 1);
int oldLine = cursorLineInDocument() - cursorLineOnScreen();
EDITOR(setTextCursor(m_tc));
if (EDITOR(find(needle, flags))) {
m_tc = EDITOR(textCursor());
m_tc.setPosition(m_tc.anchor());
// making this unconditional feels better, but is not "vim like"
if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
highlightMatches(needle);
} else {
m_tc.setPosition(forward ? 0 : lastPositionInDocument());
EDITOR(setTextCursor(m_tc));
if (EDITOR(find(needle, flags))) {
m_tc = EDITOR(textCursor());
m_tc.setPosition(m_tc.anchor());
if (oldLine != cursorLineInDocument() - cursorLineOnScreen())
scrollToLineInDocument(cursorLineInDocument() - linesOnScreen() / 2);
if (forward)
showRedMessage(FakeVimHandler::tr("search hit BOTTOM, continuing at TOP"));
else
showRedMessage(FakeVimHandler::tr("search hit TOP, continuing at BOTTOM"));
highlightMatches(needle);
} else {
highlightMatches(QString());
setCursorPosition(origPosition);
showRedMessage(FakeVimHandler::tr("Pattern not found: ") + needle);
}
}
}
void FakeVimHandler::Private::highlightMatches(const QString &needle0)
{
if (!hasConfig(ConfigHlSearch))
return;
if (needle0 == m_oldNeedle)
return;
m_oldNeedle = needle0;
m_searchSelections.clear();
if (!needle0.isEmpty()) {
QTextCursor tc = m_tc;
tc.movePosition(StartOfDocument, MoveAnchor);
QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
QString needle = needle0;
vimPatternToQtPattern(&needle, &flags);
EDITOR(setTextCursor(tc));
while (EDITOR(find(needle, flags))) {
tc = EDITOR(textCursor());
QTextEdit::ExtraSelection sel;
sel.cursor = tc;
sel.format = tc.blockCharFormat();
sel.format.setBackground(QColor(177, 177, 0));
m_searchSelections.append(sel);
tc.movePosition(Right, MoveAnchor);
EDITOR(setTextCursor(tc));
}
}
updateSelection();
}
void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
{
QTextDocument *doc = m_tc.document();
const QTextBlock &block = m_tc.block();
int firstPos = block.position();
for (int i = firstPos, n = firstPos + block.length(); i < n; ++i) {
if (!doc->characterAt(i).isSpace()) {
setPosition(i);
return;
}
}
setPosition(block.position());
}
void FakeVimHandler::Private::indentRegion(QChar typedChar)
{
//int savedPos = anchor();
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
if (beginLine > endLine)
qSwap(beginLine, endLine);
int amount = 0;
emit q->indentRegion(&amount, beginLine, endLine, typedChar);
setPosition(firstPositionInLine(beginLine));
moveToFirstNonBlankOnLine();
setTargetColumn();
setDotCommand("%1==", endLine - beginLine + 1);
}
void FakeVimHandler::Private::shiftRegionRight(int repeat)
{
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
if (beginLine > endLine)
qSwap(beginLine, endLine);
int len = config(ConfigShiftWidth).toInt() * repeat;
QString indent(len, ' ');
int firstPos = firstPositionInLine(beginLine);
beginEditBlock(firstPos);
for (int line = beginLine; line <= endLine; ++line) {
setPosition(firstPositionInLine(line));
m_tc.insertText(indent);
}
endEditBlock();
setPosition(firstPos);
moveToFirstNonBlankOnLine();
setTargetColumn();
setDotCommand("%1>>", endLine - beginLine + 1);
}
void FakeVimHandler::Private::shiftRegionLeft(int repeat)
{
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
if (beginLine > endLine)
qSwap(beginLine, endLine);
int shift = config(ConfigShiftWidth).toInt() * repeat;
int tab = config(ConfigTabStop).toInt();
int firstPos = firstPositionInLine(beginLine);
beginEditBlock(firstPos);
for (int line = beginLine; line <= endLine; ++line) {
int pos = firstPositionInLine(line);
setPosition(pos);
setAnchor(pos);
QString text = m_tc.block().text();
int amount = 0;
int i = 0;
for (; i < text.size() && amount <= shift; ++i) {
if (text.at(i) == ' ')
amount++;
else if (text.at(i) == '\t')
amount += tab; // FIXME: take position into consideration
else
break;
}
setPosition(pos + i);
text = selectedText();
removeSelectedText();
setPosition(pos);
}
endEditBlock();
setPosition(firstPos);
moveToFirstNonBlankOnLine();
setTargetColumn();
setDotCommand("%1<<", endLine - beginLine + 1);
}
void FakeVimHandler::Private::moveToTargetColumn()
{
const QTextBlock &block = m_tc.block();
int col = m_tc.position() - block.position();
if (col == m_targetColumn)
return;
//qDebug() << "CORRECTING COLUMN FROM: " << col << "TO" << m_targetColumn;
if (m_targetColumn == -1 || block.length() <= m_targetColumn)
m_tc.setPosition(block.position() + block.length() - 1, MoveAnchor);
else
m_tc.setPosition(block.position() + m_targetColumn, MoveAnchor);
}
/* if simple is given:
* class 0: spaces
* class 1: non-spaces
* else
* class 0: spaces
* class 1: non-space-or-letter-or-number
* class 2: letter-or-number
*/
static int charClass(QChar c, bool simple)
{
if (simple)
return c.isSpace() ? 0 : 1;
if (c.isLetterOrNumber() || c.unicode() == '_')
return 2;
return c.isSpace() ? 0 : 1;
}
void FakeVimHandler::Private::moveToWordBoundary(bool simple, bool forward)
{
int repeat = count();
QTextDocument *doc = m_tc.document();
int n = forward ? lastPositionInDocument() : 0;
int lastClass = -1;
while (true) {
QChar c = doc->characterAt(m_tc.position() + (forward ? 1 : -1));
//qDebug() << "EXAMINING: " << c << " AT " << position();
int thisClass = charClass(c, simple);
if (thisClass != lastClass && lastClass != 0)
--repeat;
if (repeat == -1)
break;
lastClass = thisClass;
if (m_tc.position() == n)
break;
forward ? moveRight() : moveLeft();
}
setTargetColumn();
}
void FakeVimHandler::Private::handleFfTt(int key)
{
// m_subsubmode \in { 'f', 'F', 't', 'T' }
bool forward = m_subsubdata == 'f' || m_subsubdata == 't';
int repeat = count();
QTextDocument *doc = m_tc.document();
QTextBlock block = m_tc.block();
int n = block.position();
if (forward)
n += block.length();
int pos = m_tc.position();
while (true) {
pos += forward ? 1 : -1;
if (pos == n)
break;
int uc = doc->characterAt(pos).unicode();
if (uc == ParagraphSeparator)
break;
if (uc == key)
--repeat;
if (repeat == 0) {
if (m_subsubdata == 't')
--pos;
else if (m_subsubdata == 'T')
++pos;
if (forward)
m_tc.movePosition(Right, KeepAnchor, pos - m_tc.position());
else
m_tc.movePosition(Left, KeepAnchor, m_tc.position() - pos);
break;
}
}
setTargetColumn();
}
void FakeVimHandler::Private::moveToNextWord(bool simple)
{
// FIXME: 'w' should stop on empty lines, too
int repeat = count();
int n = lastPositionInDocument();
int lastClass = charClass(characterAtCursor(), simple);
while (true) {
QChar c = characterAtCursor();
int thisClass = charClass(c, simple);
if (thisClass != lastClass && thisClass != 0)
--repeat;
if (repeat == 0)
break;
lastClass = thisClass;
moveRight();
if (m_tc.position() == n)
break;
}
setTargetColumn();
}
void FakeVimHandler::Private::moveToMatchingParanthesis()
{
bool moved = false;
bool forward = false;
emit q->moveToMatchingParenthesis(&moved, &forward, &m_tc);
if (moved && forward) {
if (m_submode == NoSubMode || m_submode == ZSubMode || m_submode == CapitalZSubMode || m_submode == RegisterSubMode)
m_tc.movePosition(Left, KeepAnchor, 1);
}
setTargetColumn();
}
int FakeVimHandler::Private::cursorLineOnScreen() const
{
if (!editor())
return 0;
QRect rect = EDITOR(cursorRect());
return rect.y() / rect.height();
}
int FakeVimHandler::Private::linesOnScreen() const
{
if (!editor())
return 1;
QRect rect = EDITOR(cursorRect());
return EDITOR(height()) / rect.height();
}
int FakeVimHandler::Private::columnsOnScreen() const
{
if (!editor())
return 1;
QRect rect = EDITOR(cursorRect());
// qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
return EDITOR(width()) / rect.width();
}
int FakeVimHandler::Private::cursorLineInDocument() const
{
return m_tc.block().blockNumber();
}
int FakeVimHandler::Private::cursorColumnInDocument() const
{
return m_tc.position() - m_tc.block().position();
}
int FakeVimHandler::Private::linesInDocument() const
{
return m_tc.isNull() ? 0 : m_tc.document()->blockCount();
}
void FakeVimHandler::Private::scrollToLineInDocument(int line)
{
// FIXME: works only for QPlainTextEdit
QScrollBar *scrollBar = EDITOR(verticalScrollBar());
//qDebug() << "SCROLL: " << scrollBar->value() << line;
scrollBar->setValue(line);
//QTC_ASSERT(firstVisibleLineInDocument() == line, /**/);
}
int FakeVimHandler::Private::firstVisibleLineInDocument() const
{
QScrollBar *scrollBar = EDITOR(verticalScrollBar());
if (0 && scrollBar->value() != cursorLineInDocument() - cursorLineOnScreen()) {
qDebug() << "SCROLLBAR: " << scrollBar->value()
<< "CURSORLINE IN DOC" << cursorLineInDocument()
<< "CURSORLINE ON SCREEN" << cursorLineOnScreen();
}
//return scrollBar->value();
return cursorLineInDocument() - cursorLineOnScreen();
}
void FakeVimHandler::Private::scrollUp(int count)
{
scrollToLineInDocument(cursorLineInDocument() - cursorLineOnScreen() - count);
}
int FakeVimHandler::Private::lastPositionInDocument() const
{
QTextBlock block = m_tc.document()->lastBlock();
return block.position() + block.length() - 1;
}
QString FakeVimHandler::Private::lastSearchString() const
{
return m_searchHistory.empty() ? QString() : m_searchHistory.back();
}
QString FakeVimHandler::Private::text(const Range &range) const
{
if (range.rangemode == RangeCharMode) {
QTextCursor tc = m_tc;
tc.setPosition(range.beginPos, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
return tc.selection().toPlainText();
}
if (range.rangemode == RangeLineMode) {
QTextCursor tc = m_tc;
int firstPos = firstPositionInLine(lineForPosition(range.beginPos));
int lastLine = lineForPosition(range.endPos);
int lastPos = lastLine == m_tc.document()->lastBlock().blockNumber() + 1
? lastPositionInDocument() : firstPositionInLine(lastLine + 1);
tc.setPosition(firstPos, MoveAnchor);
tc.setPosition(lastPos, KeepAnchor);
return tc.selection().toPlainText();
}
// FIXME: Performance?
int beginLine = lineForPosition(range.beginPos);
int endLine = lineForPosition(range.endPos);
int beginColumn = 0;
int endColumn = INT_MAX;
if (range.rangemode == RangeBlockMode) {
int column1 = range.beginPos - firstPositionInLine(beginLine);
int column2 = range.endPos - firstPositionInLine(endLine);
beginColumn = qMin(column1, column2);
endColumn = qMax(column1, column2);
qDebug() << "COLS: " << beginColumn << endColumn;
}
int len = endColumn - beginColumn + 1;
QString contents;
QTextBlock block = m_tc.document()->findBlockByNumber(beginLine - 1);
for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
QString line = block.text();
if (range.rangemode == RangeBlockMode) {
line = line.mid(beginColumn, len);
if (line.size() < len)
line += QString(len - line.size(), QChar(' '));
}
contents += line;
if (!contents.endsWith('\n'))
contents += '\n';
block = block.next();
}
//qDebug() << "SELECTED: " << contents;
return contents;
}
void FakeVimHandler::Private::yankSelectedText()
{
Range range(anchor(), position());
range.rangemode = m_rangemode;
yankText(range, m_register);
}
void FakeVimHandler::Private::yankText(const Range &range, int toregister)
{
Register &reg = m_registers[toregister];
reg.contents = text(range);
reg.rangemode = range.rangemode;
//qDebug() << "YANKED: " << reg.contents;
}
void FakeVimHandler::Private::removeSelectedText()
{
Range range(anchor(), position());
range.rangemode = m_rangemode;
removeText(range);
}
void FakeVimHandler::Private::removeText(const Range &range)
{
QTextCursor tc = m_tc;
switch (range.rangemode) {
case RangeCharMode: {
tc.setPosition(range.beginPos, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
fixMarks(range.beginPos, tc.selectionStart() - tc.selectionEnd());
tc.removeSelectedText();
return;
}
case RangeLineMode: {
tc.setPosition(range.beginPos, MoveAnchor);
tc.movePosition(StartOfLine, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
tc.movePosition(EndOfLine, KeepAnchor);
tc.movePosition(Right, KeepAnchor, 1);
fixMarks(range.beginPos, tc.selectionStart() - tc.selectionEnd());
tc.removeSelectedText();
return;
}
case RangeBlockAndTailMode:
case RangeBlockMode: {
int beginLine = lineForPosition(range.beginPos);
int endLine = lineForPosition(range.endPos);
int column1 = range.beginPos - firstPositionInLine(beginLine);
int column2 = range.endPos - firstPositionInLine(endLine);
int beginColumn = qMin(column1, column2);
int endColumn = qMax(column1, column2);
if (range.rangemode == RangeBlockAndTailMode)
endColumn = INT_MAX - 1;
QTextBlock block = m_tc.document()->findBlockByNumber(endLine - 1);
beginEditBlock(range.beginPos);
for (int i = beginLine; i <= endLine && block.isValid(); ++i) {
int bCol = qMin(beginColumn, block.length() - 1);
int eCol = qMin(endColumn + 1, block.length() - 1);
tc.setPosition(block.position() + bCol, MoveAnchor);
tc.setPosition(block.position() + eCol, KeepAnchor);
fixMarks(block.position() + bCol,
tc.selectionStart() - tc.selectionEnd());
tc.removeSelectedText();
block = block.previous();
}
endEditBlock();
}
}
}
void FakeVimHandler::Private::pasteText(bool afterCursor)
{
const QString text = m_registers[m_register].contents;
const QStringList lines = text.split(QChar('\n'));
switch (m_registers[m_register].rangemode) {
case RangeCharMode: {
m_targetColumn = 0;
for (int i = count(); --i >= 0; ) {
if (afterCursor && rightDist() > 0)
moveRight();
fixMarks(position(), text.length());
m_tc.insertText(text);
if (!afterCursor && atEndOfLine())
moveLeft();
moveLeft();
}
break;
}
case RangeLineMode: {
moveToStartOfLine();
m_targetColumn = 0;
for (int i = count(); --i >= 0; ) {
if (afterCursor)
moveDown();
fixMarks(position(), text.length());
m_tc.insertText(text);
moveUp(lines.size() - 1);
}
moveToFirstNonBlankOnLine();
break;
}
case RangeBlockAndTailMode:
case RangeBlockMode: {
beginEditBlock();
QTextBlock block = m_tc.block();
if (afterCursor)
moveRight();
QTextCursor tc = m_tc;
const int col = tc.position() - block.position();
//for (int i = lines.size(); --i >= 0; ) {
for (int i = 0; i < lines.size(); ++i) {
const QString line = lines.at(i);
tc.movePosition(StartOfLine, MoveAnchor);
if (col >= block.length()) {
tc.movePosition(EndOfLine, MoveAnchor);
fixMarks(position(), col - line.size() + 1);
tc.insertText(QString(col - line.size() + 1, QChar(' ')));
} else {
tc.movePosition(Right, MoveAnchor, col - 1 + afterCursor);
}
qDebug() << "INSERT " << line << " AT " << tc.position()
<< "COL: " << col;
fixMarks(position(), line.length());
tc.insertText(line);
tc.movePosition(StartOfLine, MoveAnchor);
tc.movePosition(Down, MoveAnchor, 1);
if (tc.position() >= lastPositionInDocument() - 1) {
fixMarks(position(), 1);
tc.insertText(QString(QChar('\n')));
tc.movePosition(Up, MoveAnchor, 1);
}
block = block.next();
}
moveLeft();
endEditBlock();
break;
}
}
}
//FIXME: This needs to called after undo/insert
void FakeVimHandler::Private::fixMarks(int positionAction, int positionChange)
{
QHashIterator<int, int> i(m_marks);
while (i.hasNext()) {
i.next();
if (i.value() >= positionAction) {
if (i.value() + positionChange > 0)
m_marks[i.key()] = i.value() + positionChange;
else
m_marks.remove(i.key());
}
}
}
QString FakeVimHandler::Private::lineContents(int line) const
{
return m_tc.document()->findBlockByNumber(line - 1).text();
}
void FakeVimHandler::Private::setLineContents(int line, const QString &contents) const
{
QTextBlock block = m_tc.document()->findBlockByNumber(line - 1);
QTextCursor tc = m_tc;
tc.setPosition(block.position());
tc.setPosition(block.position() + block.length() - 1, KeepAnchor);
tc.removeSelectedText();
tc.insertText(contents);
}
int FakeVimHandler::Private::firstPositionInLine(int line) const
{
return m_tc.document()->findBlockByNumber(line - 1).position();
}
int FakeVimHandler::Private::lastPositionInLine(int line) const
{
QTextBlock block = m_tc.document()->findBlockByNumber(line - 1);
return block.position() + block.length() - 1;
}
int FakeVimHandler::Private::lineForPosition(int pos) const
{
QTextCursor tc = m_tc;
tc.setPosition(pos);
return tc.block().blockNumber() + 1;
}
void FakeVimHandler::Private::enterVisualMode(VisualMode visualMode)
{
setAnchor();
m_visualMode = visualMode;
m_marks['<'] = m_tc.position();
m_marks['>'] = m_tc.position();
updateMiniBuffer();
updateSelection();
}
void FakeVimHandler::Private::leaveVisualMode()
{
m_visualMode = NoVisualMode;
updateMiniBuffer();
updateSelection();
}
QWidget *FakeVimHandler::Private::editor() const
{
return m_textedit
? static_cast<QWidget *>(m_textedit)
: static_cast<QWidget *>(m_plaintextedit);
}
void FakeVimHandler::Private::undo()
{
//qDebug() << " CURSOR POS: " << m_undoCursorPosition;
int current = m_tc.document()->availableUndoSteps();
//endEditBlock();
EDITOR(undo());
//beginEditBlock();
int rev = m_tc.document()->availableUndoSteps();
if (current == rev)
showBlackMessage(FakeVimHandler::tr("Already at oldest change"));
else
showBlackMessage(QString());
if (m_undoCursorPosition.contains(rev))
m_tc.setPosition(m_undoCursorPosition[rev]);
}
void FakeVimHandler::Private::redo()
{
int current = m_tc.document()->availableUndoSteps();
//endEditBlock();
EDITOR(redo());
//beginEditBlock();
int rev = m_tc.document()->availableUndoSteps();
if (rev == current)
showBlackMessage(FakeVimHandler::tr("Already at newest change"));
else
showBlackMessage(QString());
if (m_undoCursorPosition.contains(rev))
m_tc.setPosition(m_undoCursorPosition[rev]);
}
void FakeVimHandler::Private::enterInsertMode()
{
EDITOR(setCursorWidth(m_cursorWidth));
EDITOR(setOverwriteMode(false));
m_mode = InsertMode;
m_lastInsertion.clear();
m_beginEditBlock = true;
}
void FakeVimHandler::Private::enterCommandMode()
{
EDITOR(setCursorWidth(m_cursorWidth));
EDITOR(setOverwriteMode(true));
m_mode = CommandMode;
}
void FakeVimHandler::Private::enterExMode()
{
EDITOR(setCursorWidth(0));
EDITOR(setOverwriteMode(false));
m_mode = ExMode;
}
void FakeVimHandler::Private::recordJump()
{
m_jumpListUndo.append(cursorPosition());
m_jumpListRedo.clear();
UNDO_DEBUG("jumps: " << m_jumpListUndo);
}
void FakeVimHandler::Private::recordNewUndo()
{
//endEditBlock();
UNDO_DEBUG("---- BREAK ----");
//beginEditBlock();
}
Indentation FakeVimHandler::Private::indentation(const QString &line) const
{
int ts = config(ConfigTabStop).toInt();
int physical = 0;
int logical = 0;
int n = line.size();
while (physical < n) {
QChar c = line.at(physical);
if (c == QLatin1Char(' '))
++logical;
else if (c == QLatin1Char('\t'))
logical += ts - logical % ts;
else
break;
++physical;
}
return Indentation(physical, logical);
}
QString FakeVimHandler::Private::tabExpand(int n) const
{
int ts = config(ConfigTabStop).toInt();
if (hasConfig(ConfigExpandTab) || ts < 1)
return QString(n, QLatin1Char(' '));
return QString(n / ts, QLatin1Char('\t'))
+ QString(n % ts, QLatin1Char(' '));
}
void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
{
if (!hasConfig(ConfigAutoIndent))
return;
QTextBlock block = goingDown ? m_tc.block().previous() : m_tc.block().next();
QString text = block.text();
int pos = 0;
int n = text.size();
while (pos < n && text.at(pos).isSpace())
++pos;
text.truncate(pos);
// FIXME: handle 'smartindent' and 'cindent'
m_tc.insertText(text);
m_justAutoIndented = text.size();
}
bool FakeVimHandler::Private::removeAutomaticIndentation()
{
if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
return false;
m_tc.movePosition(StartOfLine, KeepAnchor);
m_tc.removeSelectedText();
m_lastInsertion.chop(m_justAutoIndented);
m_justAutoIndented = 0;
return true;
}
void FakeVimHandler::Private::handleStartOfLine()
{
if (hasConfig(ConfigStartOfLine))
moveToFirstNonBlankOnLine();
}
void FakeVimHandler::Private::replay(const QString &command, int n)
{
//qDebug() << "REPLAY: " << command;
m_inReplay = true;
for (int i = n; --i >= 0; ) {
foreach (QChar c, command) {
//qDebug() << " REPLAY: " << QString(c);
handleKey(c.unicode(), c.unicode(), QString(c));
}
}
m_inReplay = false;
}
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
: QObject(parent), d(new Private(this, widget))
{}
FakeVimHandler::~FakeVimHandler()
{
delete d;
}
bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
{
bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
if (active && ev->type() == QEvent::KeyPress && ob == d->editor()) {
QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
KEY_DEBUG("KEYPRESS" << kev->key());
EventResult res = d->handleEvent(kev);
// returning false core the app see it
//KEY_DEBUG("HANDLED CODE:" << res);
//return res != EventPassedToCore;
//return true;
return res == EventHandled;
}
if (active && ev->type() == QEvent::ShortcutOverride && ob == d->editor()) {
QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
if (d->wantsOverride(kev)) {
KEY_DEBUG("OVERRIDING SHORTCUT" << kev->key());
ev->accept(); // accepting means "don't run the shortcuts"
return true;
}
KEY_DEBUG("NO SHORTCUT OVERRIDE" << kev->key());
return true;
}
return QObject::eventFilter(ob, ev);
}
void FakeVimHandler::installEventFilter()
{
d->installEventFilter();
}
void FakeVimHandler::setupWidget()
{
d->setupWidget();
}
void FakeVimHandler::restoreWidget()
{
d->restoreWidget();
}
void FakeVimHandler::handleCommand(const QString &cmd)
{
d->handleCommand(cmd);
}
void FakeVimHandler::setCurrentFileName(const QString &fileName)
{
d->m_currentFileName = fileName;
}
void FakeVimHandler::showBlackMessage(const QString &msg)
{
d->showBlackMessage(msg);
}
void FakeVimHandler::showRedMessage(const QString &msg)
{
d->showRedMessage(msg);
}
QWidget *FakeVimHandler::widget()
{
return d->editor();
}
// Test only
int FakeVimHandler::physicalIndentation(const QString &line) const
{
Indentation ind = d->indentation(line);
return ind.physical;
}
int FakeVimHandler::logicalIndentation(const QString &line) const
{
Indentation ind = d->indentation(line);
return ind.logical;
}
QString FakeVimHandler::tabExpand(int n) const
{
return d->tabExpand(n);
}
} // namespace Internal
} // namespace FakeVim