Files
qt-creator/src/plugins/fakevim/fakevimhandler.cpp

6375 lines
201 KiB
C++
Raw Normal View History

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: http://www.qt-project.org/
**
**
** GNU Lesser General Public License Usage
**
2011-04-13 08:42:33 +02:00
** 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.
**
2010-12-17 16:01:08 +01:00
** In addition, as a special exception, Nokia gives you certain additional
2011-04-13 08:42:33 +02:00
** rights. These rights are described in the Nokia Qt LGPL Exception
2010-12-17 16:01:08 +01:00
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
2011-04-13 08:42:33 +02:00
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**************************************************************************/
//
// 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.
//
// A QTextCursor is always between characters, whereas vi's cursor is always
// over a character. FakeVim interprets the QTextCursor to be over the character
// to the right of the QTextCursor's position().
//
// A current "region of interest"
2010-09-14 14:04:13 +02:00
// spans between anchor(), (i.e. the character below anchor()), and
// position(). The character below position() is not included
// if the last movement command was exclusive (MoveExclusive).
2010-05-06 14:08:09 +02:00
//
#include "fakevimhandler.h"
#include <utils/hostosinfo.h>
2009-03-30 12:40:08 +02:00
#include <utils/qtcassert.h>
#include <QDebug>
#include <QFile>
#include <QObject>
#include <QPointer>
#include <QProcess>
#include <QRegExp>
#include <QTextStream>
#include <QTimer>
#include <QtAlgorithms>
#include <QStack>
2008-12-19 16:20:39 +01:00
#include <QApplication>
#include <QClipboard>
#include <QInputMethodEvent>
#include <QKeyEvent>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QScrollBar>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QTextEdit>
#include <QMimeData>
#include <algorithm>
#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() << << revision() << s
#else
# define UNDO_DEBUG(s)
#endif
using namespace Utils;
2009-03-30 12:40:08 +02:00
namespace FakeVim {
namespace Internal {
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
2009-04-14 15:47:25 +02:00
#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 ParagraphSeparator QChar::ParagraphSeparator
#define EDITOR(s) (m_textedit ? m_textedit->s : m_plaintextedit->s)
#define MetaModifier // Use HostOsInfo::controlModifier() instead
#define ControlModifier // Use HostOsInfo::controlModifier() instead
2010-05-11 14:26:37 +02:00
typedef QLatin1String _;
/* Clipboard MIME types used by Vim. */
static const QString vimMimeText = "_VIM_TEXT";
static const QString vimMimeTextEncoded = "_VIMENC_TEXT";
using namespace Qt;
2010-05-06 14:08:09 +02:00
/*! A \e Mode represents one of the basic modes of operation of FakeVim.
*/
2008-12-19 16:20:39 +01:00
enum Mode
{
InsertMode,
ReplaceMode,
2008-12-19 16:20:39 +01:00
CommandMode,
ExMode
2008-12-19 16:20:39 +01:00
};
2010-05-06 14:08:09 +02:00
/*! A \e SubMode is used for things that require one more data item
and are 'nested' behind a \l Mode.
*/
2008-12-19 16:20:39 +01:00
enum SubMode
{
NoSubMode,
2010-07-14 13:02:17 +02:00
ChangeSubMode, // Used for c
DeleteSubMode, // Used for d
FilterSubMode, // Used for !
IndentSubMode, // Used for =
RegisterSubMode, // Used for "
ShiftLeftSubMode, // Used for <
ShiftRightSubMode, // Used for >
TransformSubMode, // Used for ~/gu/gU
WindowSubMode, // Used for Ctrl-w
YankSubMode, // Used for y
ZSubMode, // Used for z
CapitalZSubMode, // Used for Z
ReplaceSubMode, // Used for r
OpenSquareSubMode, // Used for [
CloseSquareSubMode // Used for ]
2008-12-19 16:20:39 +01:00
};
2010-05-06 14:08:09 +02:00
/*! A \e SubSubMode is used for things that require one more data item
and are 'nested' behind a \l SubMode.
*/
2008-12-26 00:18:03 +01:00
enum SubSubMode
{
NoSubSubMode,
2010-05-06 14:08:09 +02:00
FtSubSubMode, // Used for f, F, t, T.
MarkSubSubMode, // Used for m.
BackTickSubSubMode, // Used for `.
TickSubSubMode, // Used for '.
InvertCaseSubSubMode, // Used for ~.
DownCaseSubSubMode, // Used for gu.
UpCaseSubSubMode, // Used for gU.
TextObjectSubSubMode, // Used for thing like iw, aW, as etc.
SearchSubSubMode
2008-12-26 00:18:03 +01:00
};
enum VisualMode
{
NoVisualMode,
VisualCharMode,
VisualLineMode,
VisualBlockMode
};
2009-01-16 16:57:00 +01:00
enum MoveType
{
MoveExclusive,
MoveInclusive,
MoveLineWise
2009-01-16 16:57:00 +01:00
};
2010-05-06 14:08:09 +02:00
/*!
\enum RangeMode
The \e RangeMode serves as a means to define how the "Range" between
the \l cursor and the \l anchor position is to be interpreted.
\value RangeCharMode Entered by pressing \key v. The range includes
all characters between cursor and anchor.
\value RangeLineMode Entered by pressing \key V. The range includes
all lines between the line of the cursor and
the line of the anchor.
\value RangeLineModeExclusice Like \l RangeLineMode, but keeps one
newline when deleting.
\value RangeBlockMode Entered by pressing \key Ctrl-v. The range includes
all characters with line and column coordinates
between line and columns coordinates of cursor and
anchor.
\value RangeBlockAndTailMode Like \l RangeBlockMode, but also includes
all characters in the affected lines up to the end
of these lines.
*/
enum EventResult
{
EventHandled,
EventUnhandled,
EventPassedToCore
};
typedef QHash<int, QTextCursor> Marks;
struct State
{
State() : revision(-1), position(-1), line(-1), marks() {}
State(int revision, int position, int line, const Marks &marks)
: revision(revision), position(position), line(line), marks(marks) {}
int revision;
int position;
int line;
Marks marks;
};
struct Column
{
Column(int p, int l) : physical(p), logical(l) {}
2010-05-06 14:08:09 +02:00
int physical; // Number of characters in the data.
int logical; // Column on screen.
};
QDebug operator<<(QDebug ts, const Column &col)
{
return ts << "(p: " << col.physical << ", l: " << col.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) {}
2010-05-11 14:26:37 +02:00
Register(const QString &c) : contents(c), rangemode(RangeCharMode) {}
Register(const QString &c, RangeMode m) : contents(c), rangemode(m) {}
QString contents;
RangeMode rangemode;
};
2010-05-20 14:08:11 +02:00
QDebug operator<<(QDebug ts, const Register &reg)
{
return ts << reg.contents;
}
struct SearchData
{
2011-02-18 15:31:31 +01:00
SearchData()
{
forward = true;
highlightMatches = true;
}
QString needle;
bool forward;
bool highlightMatches;
};
// If string begins with given prefix remove it with trailing spaces and return true.
static bool eatString(const QString &prefix, QString *str)
{
if (!str->startsWith(prefix))
return false;
*str = str->mid(prefix.size()).trimmed();
return true;
}
static QRegExp vimPatternToQtPattern(QString needle, bool smartcase)
{
/* Transformations (Vim regexp -> QRegExp):
* \a -> [A-Za-z]
* \A -> [^A-Za-z]
* \h -> [A-Za-z_]
* \H -> [^A-Za-z_]
* \l -> [a-z]
* \L -> [^a-z]
* \o -> [0-7]
* \O -> [^0-7]
* \u -> [A-Z]
* \U -> [^A-Z]
* \x -> [0-9A-Fa-f]
* \X -> [^0-9A-Fa-f]
*
* \< -> \b
* \> -> \b
* [] -> \[\]
* \= -> ?
*
* (...) <-> \(...\)
* {...} <-> \{...\}
* | <-> \|
* ? <-> \?
* + <-> \+
* \{...} -> {...}
*
* \c - set ignorecase for rest
* \C - set noignorecase for rest
*/
bool ignorecase = smartcase && !needle.contains(QRegExp("[A-Z]"));
QString pattern;
pattern.reserve(2 * needle.size());
bool escape = false;
bool brace = false;
bool curly = false;
foreach (const QChar &c, needle) {
if (brace) {
brace = false;
if (c == ']') {
pattern.append(_("\\[\\]"));
continue;
} else {
pattern.append('[');
}
}
if (QString("(){}+|?").indexOf(c) != -1) {
if (c == '{') {
curly = escape;
} else if (c == '}' && curly) {
curly = false;
escape = true;
}
if (escape)
escape = false;
else
pattern.append('\\');
pattern.append(c);
} else if (escape) {
// escape expression
escape = false;
if (c == '<' || c == '>')
pattern.append(_("\\b"));
else if (c == 'a')
pattern.append(_("[a-zA-Z]"));
else if (c == 'A')
pattern.append(_("[^a-zA-Z]"));
else if (c == 'h')
pattern.append(_("[A-Za-z_]"));
else if (c == 'H')
pattern.append(_("[^A-Za-z_]"));
else if (c == 'c' || c == 'C')
ignorecase = (c == 'c');
else if (c == 'l')
pattern.append(_("[a-z]"));
else if (c == 'L')
pattern.append(_("[^a-z]"));
else if (c == 'o')
pattern.append(_("[0-7]"));
else if (c == 'O')
pattern.append(_("[^0-7]"));
else if (c == 'u')
pattern.append(_("[A-Z]"));
else if (c == 'U')
pattern.append(_("[^A-Z]"));
else if (c == 'x')
pattern.append(_("[0-9A-Fa-f]"));
else if (c == 'X')
pattern.append(_("[^0-9A-Fa-f]"));
else if (c == '=')
pattern.append(_("?"));
else
pattern.append('\\' + c);
} else {
// unescaped expression
if (c == '\\')
escape = true;
else if (c == '[')
brace = true;
else if (c.isLetter() && ignorecase)
pattern.append('[' + c.toLower() + c.toUpper() + ']');
else
pattern.append(c);
}
}
if (escape)
pattern.append('\\');
else if (brace)
pattern.append('[');
return QRegExp(pattern);
}
static void setClipboardData(const QString &content, RangeMode mode,
QClipboard::Mode clipboardMode)
{
QClipboard *clipboard = QApplication::clipboard();
char vimRangeMode = mode;
QByteArray bytes1;
bytes1.append(vimRangeMode);
bytes1.append(content.toUtf8());
QByteArray bytes2;
bytes2.append(vimRangeMode);
bytes2.append("utf-8");
bytes2.append('\0');
bytes2.append(content.toUtf8());
QMimeData *data = new QMimeData;
data->setText(content);
data->setData(vimMimeText, bytes1);
data->setData(vimMimeTextEncoded, bytes2);
clipboard->setMimeData(data, clipboardMode);
}
Range::Range()
: beginPos(-1), endPos(-1), rangemode(RangeCharMode)
{}
Range::Range(int b, int e, RangeMode m)
: beginPos(qMin(b, e)), endPos(qMax(b, e)), rangemode(m)
{}
QString Range::toString() const
2010-05-12 11:18:18 +02:00
{
return QString("%1-%2 (mode: %3)").arg(beginPos).arg(endPos)
.arg(rangemode);
}
QDebug operator<<(QDebug ts, const Range &range)
{
return ts << '[' << range.beginPos << ',' << range.endPos << ']';
}
ExCommand::ExCommand(const QString &c, const QString &a, const Range &r)
: cmd(c), hasBang(false), args(a), range(r), count(1)
{}
2010-05-12 11:18:18 +02:00
bool ExCommand::matches(const QString &min, const QString &full) const
{
return cmd.startsWith(min) && full.startsWith(cmd);
}
void ExCommand::setContentsFromLine(const QString &line)
{
// split command to subcommands
subCommands = line.split('|');
}
bool ExCommand::nextSubcommand()
{
cmd.clear();
while (cmd.isEmpty() && !subCommands.isEmpty()) {
cmd = subCommands.takeFirst().trimmed();
cmd.remove(QRegExp("^:+\\s*")); // remove leading colons
hasBang = cmd.endsWith('!');
if (hasBang)
cmd.chop(1);
// command arguments
args = cmd.section(QRegExp("\\s+"), 1);
if (!args.isEmpty())
cmd = cmd.left(cmd.size() - args.size()).trimmed();
}
return !cmd.isEmpty();
}
QString ExCommand::printCommand() const
{
return subCommands.isEmpty() ? cmd : cmd + "|" + subCommands.join("|");
}
2010-05-12 11:18:18 +02:00
QDebug operator<<(QDebug ts, const ExCommand &cmd)
{
return ts << cmd.cmd << ' ' << cmd.args << ' ' << cmd.range;
2010-05-12 11:18:18 +02:00
}
QDebug operator<<(QDebug ts, const QList<QTextEdit::ExtraSelection> &sels)
{
foreach (const QTextEdit::ExtraSelection &sel, sels)
ts << "SEL: " << sel.cursor.anchor() << sel.cursor.position();
return ts;
}
2009-06-11 17:09:05 +02:00
QString quoteUnprintable(const QString &ba)
2009-01-16 16:15:01 +01:00
{
2009-06-11 17:09:05 +02:00
QString res;
for (int i = 0, n = ba.size(); i != n; ++i) {
const QChar c = ba.at(i);
const int cc = c.unicode();
2009-06-11 17:09:05 +02:00
if (c.isPrint())
res += c;
else if (cc == '\n')
2010-05-11 14:26:37 +02:00
res += _("<CR>");
2009-06-11 17:09:05 +02:00
else
res += QString("\\x%1").arg(c.unicode(), 2, 16, QLatin1Char('0'));
2009-06-11 17:09:05 +02:00
}
return res;
2009-01-16 16:15:01 +01:00
}
static bool startsWithWhitespace(const QString &str, int col)
{
QTC_ASSERT(str.size() >= col, return false);
for (int i = 0; i < col; ++i) {
uint u = str.at(i).unicode();
if (u != ' ' && u != '\t')
return false;
}
return true;
}
inline QString msgMarkNotSet(const QString &text)
2009-10-07 09:25:19 +02:00
{
2010-05-11 14:26:37 +02:00
return FakeVimHandler::tr("Mark '%1' not set").arg(text);
2009-10-07 09:25:19 +02:00
}
2010-03-26 13:22:06 +01:00
class Input
{
2010-03-26 13:22:06 +01:00
public:
// Remove some extra "information" on Mac.
static int cleanModifier(int m) { return m & ~Qt::KeypadModifier; }
Input()
: m_key(0), m_xkey(0), m_modifiers(0) {}
2010-03-26 13:22:06 +01:00
explicit Input(QChar x)
: m_key(x.unicode()), m_xkey(x.unicode()), m_modifiers(0), m_text(x)
{
if (x.isUpper())
m_modifiers = Qt::ShiftModifier;
else if (x.isLower())
m_key = x.toUpper().unicode();
}
2010-03-26 13:22:06 +01:00
Input(int k, int m, const QString &t)
: m_key(k), m_modifiers(cleanModifier(m)), m_text(t)
{
// On Mac, QKeyEvent::text() returns non-empty strings for
// cursor keys. This breaks some of the logic later on
// relying on text() being empty for "special" keys.
// FIXME: Check the real conditions.
if (m_text.size() == 1 && m_text.at(0).unicode() < ' ')
m_text.clear();
// m_xkey is only a cache.
m_xkey = (m_text.size() == 1 ? m_text.at(0).unicode() : m_key);
}
bool isValid() const
{
return m_key != 0 || !m_text.isNull();
}
bool isDigit() const
{
return m_xkey >= '0' && m_xkey <= '9';
}
bool isKey(int c) const
{
return !m_modifiers && m_key == c;
}
bool isBackspace() const
{
return m_key == Key_Backspace || isControl('h');
}
bool isReturn() const
{
return m_key == Key_Return || m_key == Key_Enter;
}
bool isEscape() const
{
return isKey(Key_Escape) || isKey(27) || isControl('c')
|| isControl(Key_BracketLeft);
}
bool is(int c) const
{
return m_xkey == c && m_modifiers != int(HostOsInfo::controlModifier());
}
bool isControl(int c) const
{
return m_modifiers == int(HostOsInfo::controlModifier())
&& (m_xkey == c || m_xkey + 32 == c || m_xkey + 64 == c || m_xkey + 96 == c);
}
bool isShift(int c) const
{
return m_modifiers == Qt::ShiftModifier && m_xkey == c;
}
bool operator==(const Input &a) const
{
return m_key == a.m_key && m_modifiers == a.m_modifiers && m_text == a.m_text;
}
bool operator!=(const Input &a) const { return !operator==(a); }
bool operator<(const Input &a) const
{
return m_key < a.m_key || m_modifiers < a.m_modifiers || m_text < a.m_text;
}
QString text() const { return m_text; }
2010-05-11 14:26:37 +02:00
QChar asChar() const
{
return (m_text.size() == 1 ? m_text.at(0) : QChar());
}
int key() const { return m_key; }
QChar raw() const
{
if (m_key == Key_Tab)
return '\t';
if (m_key == Key_Return)
return '\n';
return m_key;
}
QDebug dump(QDebug ts) const
{
return ts << m_key << '-' << m_modifiers << '-'
<< quoteUnprintable(m_text);
}
private:
int m_key;
int m_xkey;
int m_modifiers;
QString m_text;
};
2010-03-26 13:22:06 +01:00
// mapping to <Nop> (do nothing)
static const Input Nop(-1, -1, QString());
QDebug operator<<(QDebug ts, const Input &input) { return input.dump(ts); }
class Inputs : public QVector<Input>
{
public:
Inputs() : m_noremap(true), m_silent(false) {}
explicit Inputs(const QString &str, bool noremap = true, bool silent = false)
: m_noremap(noremap), m_silent(silent)
{
parseFrom(str);
}
bool noremap() const { return m_noremap; }
bool silent() const { return m_silent; }
private:
void parseFrom(const QString &str);
bool m_noremap;
bool m_silent;
};
static QMap<QString, int> vimKeyNames()
{
QMap<QString, int> k;
// FIXME: Should be value of mapleader.
k.insert("LEADER", Key_Backslash);
k.insert("SPACE", Key_Space);
k.insert("TAB", Key_Tab);
k.insert("NL", Key_Return);
k.insert("NEWLINE", Key_Return);
k.insert("LINEFEED", Key_Return);
k.insert("LF", Key_Return);
k.insert("CR", Key_Return);
k.insert("RETURN", Key_Return);
k.insert("ENTER", Key_Return);
k.insert("BS", Key_Backspace);
k.insert("BACKSPACE", Key_Backspace);
k.insert("ESC", Key_Escape);
k.insert("BAR", Key_Bar);
k.insert("BSLASH", Key_Backslash);
k.insert("DEL", Key_Delete);
k.insert("DELETE", Key_Delete);
k.insert("KDEL", Key_Delete);
k.insert("UP", Key_Up);
k.insert("DOWN", Key_Down);
k.insert("LEFT", Key_Left);
k.insert("RIGHT", Key_Right);
k.insert("F1", Key_F1);
k.insert("F2", Key_F2);
k.insert("F3", Key_F3);
k.insert("F4", Key_F4);
k.insert("F5", Key_F5);
k.insert("F6", Key_F6);
k.insert("F7", Key_F7);
k.insert("F8", Key_F8);
k.insert("F9", Key_F9);
k.insert("F10", Key_F10);
k.insert("F11", Key_F11);
k.insert("F12", Key_F12);
k.insert("F13", Key_F13);
k.insert("F14", Key_F14);
k.insert("F15", Key_F15);
k.insert("F16", Key_F16);
k.insert("F17", Key_F17);
k.insert("F18", Key_F18);
k.insert("F19", Key_F19);
k.insert("F20", Key_F20);
k.insert("F21", Key_F21);
k.insert("F22", Key_F22);
k.insert("F23", Key_F23);
k.insert("F24", Key_F24);
k.insert("F25", Key_F25);
k.insert("F26", Key_F26);
k.insert("F27", Key_F27);
k.insert("F28", Key_F28);
k.insert("F29", Key_F29);
k.insert("F30", Key_F30);
k.insert("F31", Key_F31);
k.insert("F32", Key_F32);
k.insert("F33", Key_F33);
k.insert("F34", Key_F34);
k.insert("F35", Key_F35);
k.insert("INSERT", Key_Insert);
k.insert("INS", Key_Insert);
k.insert("KINSERT", Key_Insert);
k.insert("HOME", Key_Home);
k.insert("END", Key_End);
k.insert("PAGEUP", Key_PageUp);
k.insert("PAGEDOWN", Key_PageDown);
k.insert("KPLUS", Key_Plus);
k.insert("KMINUS", Key_Minus);
k.insert("KDIVIDE", Key_Slash);
k.insert("KMULTIPLY", Key_Asterisk);
k.insert("KENTER", Key_Enter);
k.insert("KPOINT", Key_Period);
return k;
}
static Input parseVimKeyName(const QString &keyName)
{
if (keyName.length() == 1)
return Input(keyName.at(0));
const QStringList keys = keyName.split('-');
const int len = keys.length();
if (len == 1 && keys.at(0) == _("nop"))
return Nop;
int mods = NoModifier;
for (int i = 0; i < len - 1; ++i) {
const QString &key = keys[i].toUpper();
if (key == "S")
mods |= Qt::ShiftModifier;
else if (key == "C")
mods |= HostOsInfo::controlModifier();
else
return Input();
}
if (!keys.isEmpty()) {
const QString key = keys.last();
if (key.length() == 1) {
// simple character
QChar c = key.at(0).toUpper();
return Input(c.unicode(), mods, QString(c));
}
// find key name
static const QMap<QString, int> k = vimKeyNames();
QMap<QString, int>::ConstIterator it = k.constFind(key.toUpper());
if (it != k.end())
return Input(*it, mods, *it <= 0x7f ? QString(QChar::fromLatin1(*it)) : QString(""));
}
return Input();
}
void Inputs::parseFrom(const QString &str)
{
const int n = str.size();
for (int i = 0; i < n; ++i) {
uint c = str.at(i).unicode();
if (c == '<') {
int j = str.indexOf('>', i);
Input input;
if (j != -1)
input = parseVimKeyName(str.mid(i+1, j - i - 1));
if (input.isValid()) {
append(input);
i = j;
} else {
append(Input(QLatin1Char(c)));
}
} else {
append(Input(QLatin1Char(c)));
}
}
}
2010-03-26 13:22:06 +01:00
class History
{
public:
History() : m_items(QString()), m_index(0) {}
void append(const QString &item);
const QString &move(const QStringRef &prefix, int skip);
const QString &current() const { return m_items[m_index]; }
const QStringList &items() const { return m_items; }
void restart() { m_index = m_items.size() - 1; }
private:
// Last item is always empty or current search prefix.
QStringList m_items;
int m_index;
};
void History::append(const QString &item)
{
if (item.isEmpty())
return;
m_items.pop_back();
m_items.removeAll(item);
m_items << item << QString();
restart();
}
const QString &History::move(const QStringRef &prefix, int skip)
{
if (!current().startsWith(prefix))
restart();
if (m_items.last() != prefix)
m_items[m_items.size() - 1] = prefix.toString();
int i = m_index + skip;
if (!prefix.isEmpty())
for (; i >= 0 && i < m_items.size() && !m_items[i].startsWith(prefix); i += skip);
if (i >= 0 && i < m_items.size())
m_index = i;
return current();
}
// Command line buffer with prompt (i.e. :, / or ? characters), text contents and cursor position.
class CommandBuffer
{
public:
CommandBuffer() : m_pos(0), m_userPos(0), m_historyAutoSave(true) {}
void setPrompt(const QChar &prompt) { m_prompt = prompt; }
void setContents(const QString &s) { m_buffer = s; m_pos = s.size(); }
void setContents(const QString &s, int pos) { m_buffer = s; m_pos = m_userPos = pos; }
QStringRef userContents() const { return m_buffer.leftRef(m_userPos); }
const QChar &prompt() const { return m_prompt; }
const QString &contents() const { return m_buffer; }
bool isEmpty() const { return m_buffer.isEmpty(); }
int cursorPos() const { return m_pos; }
void insertChar(QChar c) { m_buffer.insert(m_pos++, c); m_userPos = m_pos; }
void insertText(const QString &s)
{
m_buffer.insert(m_pos, s); m_userPos = m_pos = m_pos + s.size();
}
void deleteChar() { if (m_pos) m_buffer.remove(--m_pos, 1); m_userPos = m_pos; }
void moveLeft() { if (m_pos) m_userPos = --m_pos; }
void moveRight() { if (m_pos < m_buffer.size()) m_userPos = ++m_pos; }
void moveStart() { m_userPos = m_pos = 0; }
void moveEnd() { m_userPos = m_pos = m_buffer.size(); }
void setHistoryAutoSave(bool autoSave) { m_historyAutoSave = autoSave; }
void historyDown() { setContents(m_history.move(userContents(), 1)); }
void historyUp() { setContents(m_history.move(userContents(), -1)); }
const QStringList &historyItems() const { return m_history.items(); }
void historyPush(const QString &item = QString())
{
m_history.append(item.isNull() ? contents() : item);
}
void clear()
{
if (m_historyAutoSave)
historyPush();
m_buffer.clear();
m_userPos = m_pos = 0;
}
QString display() const
{
QString msg(m_prompt);
for (int i = 0; i != m_buffer.size(); ++i) {
const QChar c = m_buffer.at(i);
if (c.unicode() < 32) {
msg += '^';
msg += QLatin1Char(c.unicode() + 64);
} else {
msg += c;
}
}
return msg;
}
bool handleInput(const Input &input)
{
if (input.isKey(Key_Left)) {
moveLeft();
} else if (input.isKey(Key_Right)) {
moveRight();
} else if (input.isKey(Key_Home)) {
moveStart();
} else if (input.isKey(Key_End)) {
moveEnd();
} else if (input.isKey(Key_Delete)) {
if (m_pos < m_buffer.size())
m_buffer.remove(m_pos, 1);
else
deleteChar();
} else if (!input.text().isEmpty()) {
insertText(input.text());
} else {
return false;
}
return true;
}
private:
QString m_buffer;
QChar m_prompt;
History m_history;
int m_pos;
int m_userPos; // last position of inserted text (for retrieving history items)
bool m_historyAutoSave; // store items to history on clear()?
};
// Mappings for a specific mode (trie structure)
class ModeMapping : public QMap<Input, ModeMapping>
2010-03-26 13:22:06 +01:00
{
public:
const Inputs &value() const { return m_value; }
void setValue(const Inputs &value) { m_value = value; }
private:
Inputs m_value;
};
2010-03-26 13:22:06 +01:00
// Mappings for all modes
typedef QHash<char, ModeMapping> Mappings;
2010-03-26 13:22:06 +01:00
// Iterator for mappings
class MappingsIterator : public QVector<ModeMapping::Iterator>
{
public:
MappingsIterator(Mappings *mappings, char mode = -1, const Inputs &inputs = Inputs())
: m_parent(mappings)
2010-03-26 13:22:06 +01:00
{
reset(mode);
walk(inputs);
2010-03-26 13:22:06 +01:00
}
// Reset iterator state. Keep previous mode if 0.
void reset(char mode = 0)
2010-03-26 13:22:06 +01:00
{
clear();
m_lastValid = -1;
m_invalidInputCount = 0;
if (mode != 0) {
m_mode = mode;
if (mode != -1)
m_modeMapping = m_parent->find(mode);
}
2010-03-26 13:22:06 +01:00
}
bool isValid() const { return !empty(); }
// Return true if mapping can be extended.
bool canExtend() const { return isValid() && !last()->empty(); }
// Return true if this mapping can be used.
bool isComplete() const { return m_lastValid != -1; }
// Return size of current map.
int mapLength() const { return m_lastValid + 1; }
int invalidInputCount() const { return m_invalidInputCount; }
bool walk(const Input &input)
2010-03-26 13:22:06 +01:00
{
if (m_modeMapping == m_parent->end())
return false;
if (!input.isValid()) {
m_invalidInputCount += 1;
return true;
}
ModeMapping::Iterator it;
if (isValid()) {
it = last()->find(input);
if (it == last()->end())
return false;
} else {
it = m_modeMapping->find(input);
if (it == m_modeMapping->end())
return false;
2010-03-26 13:22:06 +01:00
}
if (!it->value().isEmpty())
m_lastValid = size();
append(it);
2010-03-26 13:22:06 +01:00
return true;
}
bool walk(const Inputs &inputs)
2010-03-26 13:22:06 +01:00
{
foreach (const Input &input, inputs) {
if (!walk(input))
2010-03-26 13:22:06 +01:00
return false;
}
return true;
}
// Return current mapped value. Iterator must be valid.
const Inputs &inputs() const
{
return at(m_lastValid)->value();
}
void remove()
{
if (isValid()) {
if (canExtend()) {
last()->setValue(Inputs());
} else {
if (size() > 1) {
while (last()->empty()) {
at(size() - 2)->erase(last());
pop_back();
if (size() == 1 || !last()->value().isEmpty())
break;
}
if (last()->empty() && last()->value().isEmpty())
m_modeMapping->erase(last());
} else if (last()->empty() && !last()->value().isEmpty()) {
m_modeMapping->erase(last());
}
}
}
}
void setInputs(const Inputs &key, const Inputs &inputs, bool unique = false)
{
ModeMapping *current = &(*m_parent)[m_mode];
foreach (const Input &input, key)
current = &(*current)[input];
if (!unique || current->value().isEmpty())
current->setValue(inputs);
}
private:
Mappings *m_parent;
Mappings::Iterator m_modeMapping;
int m_lastValid;
int m_invalidInputCount;
char m_mode;
2010-03-26 13:22:06 +01:00
};
// state of current mapping
struct MappingState {
MappingState()
: maxMapDepth(1000), noremap(false), silent(false) {}
MappingState(int depth, bool noremap, bool silent)
: maxMapDepth(depth), noremap(noremap), silent(silent) {}
int maxMapDepth;
bool noremap;
bool silent;
};
2010-05-05 15:50:05 +02:00
2010-03-26 13:22:06 +01:00
class FakeVimHandler::Private : public QObject
{
Q_OBJECT
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 installEventFilter();
void passShortcuts(bool enable);
void setupWidget();
void restoreWidget(int tabSize);
friend class FakeVimHandler;
void init();
EventResult handleKey(const Input &input);
EventResult handleDefaultKey(const Input &input);
Q_SLOT void handleMappedKeys();
2010-03-26 13:22:06 +01:00
EventResult handleInsertMode(const Input &);
EventResult handleReplaceMode(const Input &);
2010-03-26 13:22:06 +01:00
EventResult handleCommandMode(const Input &);
2011-05-16 17:21:43 +02:00
EventResult handleCommandMode1(const Input &);
EventResult handleCommandMode2(const Input &);
2010-03-26 13:22:06 +01:00
EventResult handleRegisterMode(const Input &);
2010-05-05 16:23:39 +02:00
EventResult handleExMode(const Input &);
2010-07-14 13:02:17 +02:00
EventResult handleOpenSquareSubMode(const Input &);
EventResult handleCloseSquareSubMode(const Input &);
2010-05-05 16:23:39 +02:00
EventResult handleSearchSubSubMode(const Input &);
2010-03-26 13:22:06 +01:00
EventResult handleCommandSubSubMode(const Input &);
void finishMovement(const QString &dotCommand = QString());
void finishMovement(const QString &dotCommand, int count);
void resetCommandMode();
void search(const SearchData &sd, bool showMessages = true);
void searchNext(bool forward = true);
2010-07-14 13:02:17 +02:00
void searchBalanced(bool forward, QChar needle, QChar other);
2009-03-12 18:05:36 +01:00
void highlightMatches(const QString &needle);
void stopIncrementalFind();
void updateFind(bool isComplete);
2008-12-25 22:41:09 +01:00
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(); }
2010-09-13 15:23:20 +02:00
QTextBlock block() const { return cursor().block(); }
int leftDist() const { return position() - block().position(); }
int rightDist() const { return block().length() - leftDist() - 1; }
bool atBlockStart() const { return cursor().atBlockStart(); }
2010-09-14 14:04:13 +02:00
bool atBlockEnd() const { return cursor().atBlockEnd(); }
bool atEndOfLine() const { return atBlockEnd() && block().length() > 1; }
bool atDocumentEnd() const { return cursor().atEnd(); }
bool atDocumentStart() const { return cursor().atStart(); }
bool atEmptyLine(const QTextCursor &tc = QTextCursor()) const;
bool atBoundary(bool end, bool simple, bool onlyWords = false,
const QTextCursor &tc = QTextCursor()) const;
bool atWordBoundary(bool end, bool simple, const QTextCursor &tc = QTextCursor()) const;
bool atWordStart(bool simple, const QTextCursor &tc = QTextCursor()) const;
bool atWordEnd(bool simple, const QTextCursor &tc = QTextCursor()) const;
bool isFirstNonBlankOnLine(int pos);
2008-12-19 16:20:39 +01:00
2010-05-12 11:18:18 +02:00
int lastPositionInDocument() const; // Returns last valid position 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
2010-05-11 14:26:37 +02:00
void setLineContents(int line, const QString &contents); // 1 based line
int blockBoundary(const QString &left, const QString &right,
bool end, int count) const; // end or start position of current code block
2009-01-06 11:11:31 +01:00
int linesOnScreen() const;
int columnsOnScreen() const;
int linesInDocument() const;
2010-05-12 11:18:18 +02:00
// The following use all zero-based counting.
int cursorLineOnScreen() const;
int cursorLine() const;
int physicalCursorColumn() const; // as stored in the data
int logicalCursorColumn() const; // as visible on screen
int physicalToLogicalColumn(int physical, const QString &text) const;
int logicalToPhysicalColumn(int logical, const QString &text) const;
Column cursorColumn() const; // as visible on screen
int firstVisibleLine() const;
void scrollToLine(int line);
2009-03-20 08:44:52 +01:00
void scrollUp(int count);
void scrollDown(int count) { scrollUp(-count); }
CursorPosition cursorPosition() const
{ return CursorPosition(position(), firstVisibleLine()); }
void setCursorPosition(const CursorPosition &p)
{ setPosition(p.position); scrollToLine(p.scrollLine); }
2010-05-12 11:18:18 +02:00
// Helper functions for indenting/
bool isElectricCharacter(QChar c) const;
void indentSelectedText(QChar lastTyped = QChar());
void indentText(const Range &range, QChar lastTyped = QChar());
2009-03-06 13:03:33 +01:00
void shiftRegionLeft(int repeat = 1);
void shiftRegionRight(int repeat = 1);
2008-12-19 14:35:57 +01:00
void moveToFirstNonBlankOnLine();
void moveToFirstNonBlankOnLine(QTextCursor *tc);
void moveToTargetColumn();
void setTargetColumn() {
m_targetColumn = logicalCursorColumn();
m_visualTargetColumn = m_targetColumn;
//qDebug() << "TARGET: " << m_targetColumn;
}
void moveToMatchingParanthesis();
void moveToBoundary(bool simple, bool forward = true);
void moveToNextBoundary(bool end, int count, bool simple, bool forward);
void moveToNextBoundaryStart(int count, bool simple, bool forward = true);
void moveToNextBoundaryEnd(int count, bool simple, bool forward = true);
void moveToBoundaryStart(int count, bool simple, bool forward = true);
void moveToBoundaryEnd(int count, bool simple, bool forward = true);
void moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines);
void moveToNextWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToNextWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToWordStart(int count, bool simple, bool forward = true, bool emptyLines = true);
void moveToWordEnd(int count, bool simple, bool forward = true, bool emptyLines = true);
2009-01-16 14:11:44 +01:00
2010-05-12 11:18:18 +02:00
// Convenience wrappers to reduce line noise.
void moveToStartOfLine();
2009-04-08 16:05:24 +02:00
void moveToEndOfLine();
2009-07-03 11:22:18 +02:00
void moveBehindEndOfLine();
2009-04-08 16:05:24 +02:00
void moveUp(int n = 1) { moveDown(-n); }
2010-09-14 16:58:31 +02:00
void moveDown(int n = 1);
2010-09-14 14:04:13 +02:00
void dump(const char *msg) const {
qDebug() << msg << "POS: " << anchor() << position()
<< "EXT: " << m_oldExternalAnchor << m_oldExternalPosition
<< "INT: " << m_oldInternalAnchor << m_oldInternalPosition
2010-09-14 16:58:31 +02:00
<< "VISUAL: " << m_visualMode;
2010-09-14 14:04:13 +02:00
}
void moveRight(int n = 1) {
//dump("RIGHT 1");
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.movePosition(Right, KeepAnchor, n);
setCursor(tc);
2010-09-14 14:04:13 +02:00
//dump("RIGHT 2");
}
void moveLeft(int n = 1) {
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.movePosition(Left, KeepAnchor, n);
setCursor(tc);
2010-09-14 14:04:13 +02:00
}
2010-09-13 15:23:20 +02:00
void setAnchor() {
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.setPosition(tc.position(), MoveAnchor);
setCursor(tc);
2010-09-13 15:23:20 +02:00
}
void setAnchor(int position) {
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.setPosition(tc.anchor(), MoveAnchor);
tc.setPosition(position, KeepAnchor);
setCursor(tc);
2010-09-13 15:23:20 +02:00
}
void setPosition(int position) {
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.setPosition(position, KeepAnchor);
setCursor(tc);
2010-09-13 15:23:20 +02:00
}
2010-09-14 14:04:13 +02:00
void setAnchorAndPosition(int anchor, int position) {
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.setPosition(anchor, MoveAnchor);
tc.setPosition(position, KeepAnchor);
setCursor(tc);
2010-09-13 15:23:20 +02:00
}
QTextCursor cursor() const { return EDITOR(textCursor()); }
void setCursor(const QTextCursor &tc) { EDITOR(setTextCursor(tc)); }
2009-01-16 16:15:01 +01:00
bool handleFfTt(QString key);
2010-05-12 11:18:18 +02:00
// Helper function for handleExCommand returning 1 based line index.
2008-12-28 02:15:26 +01:00
int readLineCode(QString &cmd);
void enterInsertMode();
void enterReplaceMode();
void enterCommandMode();
void enterExMode();
void showMessage(MessageLevel level, const QString &msg);
void clearMessage() { showMessage(MessageInfo, QString()); }
void notImplementedYet();
void updateMiniBuffer();
void updateSelection();
void updateHighlights();
2010-09-13 15:23:20 +02:00
void updateCursorShape();
QWidget *editor() const;
QTextDocument *document() const { return EDITOR(document()); }
QChar characterAtCursor() const
2010-09-13 15:23:20 +02:00
{ return document()->characterAt(position()); }
void joinPreviousEditBlock() {
UNDO_DEBUG("JOIN");
if (m_breakEditBlock)
beginEditBlock();
else
cursor().joinPreviousEditBlock();
}
void beginEditBlock() {
UNDO_DEBUG("BEGIN EDIT BLOCK");
cursor().beginEditBlock();
setUndoPosition(false);
m_breakEditBlock = false;
}
2010-09-13 15:23:20 +02:00
void endEditBlock()
{ UNDO_DEBUG("END EDIT BLOCK"); cursor().endEditBlock(); }
void breakEditBlock() { m_breakEditBlock = true; }
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; }
char currentModeCode() const;
2010-03-09 16:12:08 +01:00
void updateEditor();
void selectTextObject(bool simple, bool inner);
void selectWordTextObject(bool inner);
void selectWORDTextObject(bool inner);
void selectSentenceTextObject(bool inner);
void selectParagraphTextObject(bool inner);
void selectBlockTextObject(bool inner, char left, char right);
void changeNumberTextObject(bool doIncrement);
void selectQuotedStringTextObject(bool inner, const QString &quote);
Q_SLOT void importSelection();
void exportSelection();
void insertInInsertMode(const QString &text);
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
bool m_firstKeyPending;
SubMode m_submode;
2008-12-26 00:18:03 +01:00
SubSubMode m_subsubmode;
Input m_subsubdata;
2010-09-14 14:04:13 +02:00
int m_oldExternalPosition; // copy from last event to check for external changes
int m_oldExternalAnchor;
int m_oldInternalPosition; // copy from last event to check for external changes
int m_oldInternalAnchor;
int m_oldPosition; // FIXME: Merge with above.
int m_register;
2008-12-25 22:41:09 +01:00
QString m_mvcount;
QString m_opcount;
MoveType m_movetype;
RangeMode m_rangemode;
int m_visualInsertCount;
bool m_fakeEnd;
2010-01-21 17:42:46 +01:00
bool m_anchorPastEnd;
bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
2008-12-27 13:39:34 +01:00
int m_gflag; // whether current command started with 'g'
QString m_currentFileName;
QString m_lastSearch;
bool m_lastSearchForward;
bool m_findPending;
int m_findStartPosition;
2008-12-27 12:24:50 +01:00
QString m_lastInsertion;
QString m_lastDeletion;
2008-12-26 17:01:21 +01:00
bool m_breakEditBlock;
2010-09-13 15:23:20 +02:00
int anchor() const { return cursor().anchor(); }
int position() const { return cursor().position(); }
struct TransformationData
{
TransformationData(const QString &s, const QVariant &d)
: from(s), extraData(d) {}
QString from;
QString to;
QVariant extraData;
};
typedef void (Private::*Transformation)(TransformationData *td);
void transformText(const Range &range, Transformation transformation,
const QVariant &extraData = QVariant());
2010-05-11 14:26:37 +02:00
void insertText(const Register &reg);
void removeText(const Range &range);
void removeTransform(TransformationData *td);
2010-05-12 11:18:18 +02:00
void invertCase(const Range &range);
void invertCaseTransform(TransformationData *td);
2010-05-12 11:18:18 +02:00
void upCase(const Range &range);
void upCaseTransform(TransformationData *td);
2010-05-12 11:18:18 +02:00
void downCase(const Range &range);
void downCaseTransform(TransformationData *td);
2010-05-12 11:18:18 +02:00
void replaceText(const Range &range, const QString &str);
void replaceByStringTransform(TransformationData *td);
void replaceByCharTransform(TransformationData *td);
2010-05-12 11:18:18 +02:00
QString selectText(const Range &range) const;
void setCurrentRange(const Range &range);
Range currentRange() const { return Range(position(), anchor(), m_rangemode); }
Range rangeFromCurrentLine() const;
void yankText(const Range &range, int toregister = '"');
void pasteText(bool afterCursor);
// undo handling
int revision() const { return document()->availableUndoSteps(); }
void undo();
void redo();
void setUndoPosition(bool overwrite = true);
// revision -> state
QStack<State> m_undo;
QStack<State> m_redo;
2009-01-06 11:43:49 +01:00
// extra data for '.'
2009-04-08 16:05:24 +02:00
void replay(const QString &text, int count);
2010-05-05 15:50:05 +02:00
void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
2009-01-06 11:43:49 +01:00
// extra data for ';'
QString m_semicolonCount;
Input m_semicolonType; // 'f', 'F', 't', 'T'
QString m_semicolonKey;
// visual modes
void toggleVisualMode(VisualMode visualMode);
void leaveVisualMode();
VisualMode m_visualMode;
2010-09-14 14:04:13 +02:00
VisualMode m_oldVisualMode;
// marks as lines
2010-05-11 14:26:37 +02:00
int mark(int code) const;
void setMark(int code, int position);
typedef QHashIterator<int, QTextCursor> MarksIterator;
Marks m_marks;
2008-12-27 21:28:22 +01:00
// vi style configuration
2009-03-30 12:40:08 +02:00
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); }
2008-12-23 21:34:21 +01:00
int m_targetColumn; // -1 if past end of line
int m_visualTargetColumn; // 'l' can move past eol in visual mode only
2009-04-03 11:54:29 +02:00
// auto-indent
QString tabExpand(int len) const;
Column indentation(const QString &line) const;
2009-04-03 11:54:29 +02:00
void insertAutomaticIndentation(bool goingDown);
bool removeAutomaticIndentation(); // true if something removed
// number of autoindented characters
int m_justAutoIndented;
void handleStartOfLine();
2009-04-03 11:54:29 +02:00
// register handling
QString registerContents(int reg) const;
void setRegister(int reg, const QString &contents, RangeMode mode);
RangeMode registerRangeMode(int reg) const;
void getRegisterType(int reg, bool *isClipboard, bool *isSelection) const;
void recordJump();
void jump(int distance);
QStack<CursorPosition> m_jumpListUndo;
QStack<CursorPosition> m_jumpListRedo;
int m_lastChangePosition;
2009-03-12 18:05:36 +01:00
QList<QTextEdit::ExtraSelection> m_extraSelections;
QTextCursor m_searchCursor;
int m_searchStartPosition;
int m_searchFromScreenLine;
2010-05-11 14:26:37 +02:00
QString m_oldNeedle;
QString m_lastSubstituteFlags;
QRegExp m_lastSubstitutePattern;
QString m_lastSubstituteReplacement;
QTextCursor m_lastSelectionCursor;
VisualMode m_lastSelectionMode;
2010-03-19 14:31:21 +01:00
bool handleExCommandHelper(ExCommand &cmd); // Returns success.
bool handleExPluginCommand(const ExCommand &cmd); // Handled by plugin?
2010-05-11 14:26:37 +02:00
bool handleExBangCommand(const ExCommand &cmd);
bool handleExDeleteCommand(const ExCommand &cmd);
bool handleExGotoCommand(const ExCommand &cmd);
bool handleExHistoryCommand(const ExCommand &cmd);
2010-05-20 14:08:11 +02:00
bool handleExRegisterCommand(const ExCommand &cmd);
2010-05-11 14:26:37 +02:00
bool handleExMapCommand(const ExCommand &cmd);
2010-07-14 16:04:10 +02:00
bool handleExNohlsearchCommand(const ExCommand &cmd);
2010-05-11 14:26:37 +02:00
bool handleExNormalCommand(const ExCommand &cmd);
bool handleExReadCommand(const ExCommand &cmd);
bool handleExRedoCommand(const ExCommand &cmd);
bool handleExUndoCommand(const ExCommand &cmd);
2010-05-11 14:26:37 +02:00
bool handleExSetCommand(const ExCommand &cmd);
bool handleExShiftCommand(const ExCommand &cmd);
2010-05-11 14:26:37 +02:00
bool handleExSourceCommand(const ExCommand &cmd);
bool handleExSubstituteCommand(const ExCommand &cmd);
bool handleExWriteCommand(const ExCommand &cmd);
bool handleExEchoCommand(const ExCommand &cmd);
2010-03-26 13:22:06 +01:00
void timerEvent(QTimerEvent *ev);
2010-04-28 16:19:51 +02:00
void setupCharClass();
int charClass(QChar c, bool simple) const;
signed char m_charClass[256];
bool m_ctrlVActive;
2010-05-05 15:50:05 +02:00
void miniBufferTextEdited(const QString &text, int cursorPos);
2010-05-05 15:50:05 +02:00
static struct GlobalData
{
GlobalData()
: mappings(), currentMap(&mappings), inputTimer(-1), currentMessageLevel(MessageInfo)
{
// default mapping state - shouldn't be removed
mapStates << MappingState();
commandBuffer.setPrompt(':');
}
2010-05-05 15:50:05 +02:00
// Repetition.
QString dotCommand;
QHash<int, Register> registers;
// All mappings.
Mappings mappings;
// Input.
Inputs pendingInput;
MappingsIterator currentMap;
int inputTimer;
int lastMapCode;
QStack<MappingState> mapStates;
// Command line buffers.
CommandBuffer commandBuffer;
CommandBuffer searchBuffer;
// Current mini buffer message.
QString currentMessage;
MessageLevel currentMessageLevel;
2010-05-05 15:50:05 +02:00
} g;
};
2010-05-05 15:50:05 +02:00
FakeVimHandler::Private::GlobalData FakeVimHandler::Private::g;
FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
{
2010-04-28 16:19:51 +02:00
//static PythonHighlighterRules pythonRules;
q = parent;
m_textedit = qobject_cast<QTextEdit *>(widget);
m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
2010-09-14 14:04:13 +02:00
//new Highlighter(document(), &pythonRules);
2009-04-08 16:05:24 +02:00
init();
}
2009-04-08 16:05:24 +02:00
void FakeVimHandler::Private::init()
{
m_mode = CommandMode;
2008-12-26 00:18:03 +01:00
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_passing = false;
m_firstKeyPending = false;
m_findPending = false;
m_findStartPosition = -1;
m_fakeEnd = false;
2010-09-14 14:04:13 +02:00
m_positionPastEnd = false;
m_anchorPastEnd = false;
m_lastSearchForward = true;
m_register = '"';
2008-12-27 13:39:34 +01:00
m_gflag = false;
m_visualMode = NoVisualMode;
2010-09-14 14:04:13 +02:00
m_oldVisualMode = NoVisualMode;
m_targetColumn = 0;
m_visualTargetColumn = 0;
m_movetype = MoveInclusive;
2009-04-03 11:54:29 +02:00
m_justAutoIndented = 0;
m_rangemode = RangeCharMode;
m_ctrlVActive = false;
2010-09-14 14:04:13 +02:00
m_oldInternalAnchor = -1;
m_oldInternalPosition = -1;
m_oldExternalAnchor = -1;
m_oldExternalPosition = -1;
m_oldPosition = -1;
m_lastChangePosition = -1;
m_breakEditBlock = false;
m_searchStartPosition = 0;
m_searchFromScreenLine = 0;
2010-04-28 16:19:51 +02:00
setupCharClass();
}
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) {
if (m_subsubmode == SearchSubSubMode)
return true;
// Not sure this feels good. People often hit Esc several times.
if (isNoVisualMode()
&& m_mode == CommandMode
&& m_submode == NoSubMode
&& m_opcount.isEmpty()
&& m_mvcount.isEmpty())
return false;
return true;
}
// We are interested in overriding most Ctrl key combinations.
if (mods == int(HostOsInfo::controlModifier())
&& !config(ConfigPassControlKey).toBool()
&& ((key >= Key_A && key <= Key_Z && key != Key_K)
|| key == Key_BracketLeft || key == Key_BracketRight)) {
2009-10-16 11:30:46 +02:00
// 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)
{
const int key = ev->key();
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) {
passShortcuts(false);
KEY_DEBUG("PASSING PLAIN KEY..." << ev->key() << ev->text());
//if (input.is(',')) { // use ',,' to leave, too.
// qDebug() << "FINISHED...";
// return EventHandled;
//}
m_passing = false;
updateMiniBuffer();
KEY_DEBUG(" PASS TO CORE");
return EventPassedToCore;
}
bool inSnippetMode = false;
QMetaObject::invokeMethod(editor(),
"inSnippetMode", Q_ARG(bool *, &inSnippetMode));
if (inSnippetMode)
return EventPassedToCore;
// Fake "End of line"
2010-09-14 14:04:13 +02:00
//m_tc = cursor();
2010-09-13 13:53:18 +02:00
//bool hasBlock = false;
//emit q->requestHasBlockSelection(&hasBlock);
//qDebug() << "IMPORT BLOCK 2:" << hasBlock;
//if (0 && hasBlock) {
// (pos > anc) ? --pos : --anc;
2010-09-13 15:23:20 +02:00
importSelection();
// Position changed externally, e.g. by code completion.
2010-09-13 15:23:20 +02:00
if (position() != m_oldPosition) {
setTargetColumn();
//qDebug() << "POSITION CHANGED EXTERNALLY";
if (m_mode == InsertMode) {
2010-09-13 15:23:20 +02:00
int dist = position() - m_oldPosition;
// Try to compensate for code completion
if (dist > 0 && dist <= physicalCursorColumn()) {
2010-09-13 15:23:20 +02:00
Range range(m_oldPosition, position());
2010-05-12 11:18:18 +02:00
m_lastInsertion.append(selectText(range));
}
} else if (!isVisualMode()) {
if (atEndOfLine())
moveLeft();
}
}
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.setVisualNavigation(true);
if (m_firstKeyPending) {
m_firstKeyPending = false;
recordJump();
}
setCursor(tc);
2008-12-27 22:50:58 +01:00
if (m_fakeEnd)
2009-01-16 17:38:15 +01:00
moveRight();
//if ((mods & RealControlModifier) != 0) {
// if (key >= Key_A && key <= Key_Z)
// key = shift(key); // make it lower case
// key = control(key);
//} else if (key >= Key_A && key <= Key_Z && (mods & Qt::ShiftModifier) == 0) {
// key = shift(key);
//}
//QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
2010-09-14 14:04:13 +02:00
// || !atBlockEnd() || block().length() <= 1,
// qDebug() << "Cursor at EOL before key handler");
EventResult result = handleKey(Input(key, mods, ev->text()));
// The command might have destroyed the editor.
if (m_textedit || m_plaintextedit) {
// We fake vi-style end-of-line behaviour
m_fakeEnd = atEndOfLine() && m_mode == CommandMode && !isVisualBlockMode();
//QTC_ASSERT(m_mode == InsertMode || m_mode == ReplaceMode
2010-09-14 14:04:13 +02:00
// || !atBlockEnd() || block().length() <= 1,
// qDebug() << "Cursor at EOL after key handler");
if (m_fakeEnd)
moveLeft();
m_oldPosition = position();
if (hasConfig(ConfigShowMarks))
updateSelection();
exportSelection();
updateCursorShape();
}
return result;
}
void FakeVimHandler::Private::installEventFilter()
{
EDITOR(viewport()->installEventFilter(q));
EDITOR(installEventFilter(q));
}
void FakeVimHandler::Private::setupWidget()
{
enterCommandMode();
if (m_textedit) {
m_textedit->setLineWrapMode(QTextEdit::NoWrap);
} else if (m_plaintextedit) {
m_plaintextedit->setLineWrapMode(QPlainTextEdit::NoWrap);
}
m_wasReadOnly = EDITOR(isReadOnly());
m_firstKeyPending = true;
2010-03-09 16:12:08 +01:00
updateEditor();
importSelection();
updateMiniBuffer();
2010-09-13 15:23:20 +02:00
updateCursorShape();
}
2010-03-09 16:12:08 +01:00
void FakeVimHandler::Private::exportSelection()
{
int pos = position();
int anc = anchor();
2010-09-14 14:04:13 +02:00
m_oldInternalPosition = pos;
m_oldInternalAnchor = anc;
if (isVisualMode()) {
if (pos >= anc)
setAnchorAndPosition(anc, pos + 1);
else
setAnchorAndPosition(anc + 1, pos);
if (m_visualMode == VisualBlockMode) {
emit q->requestSetBlockSelection(false);
emit q->requestSetBlockSelection(true);
} else if (m_visualMode == VisualLineMode) {
const int posLine = lineForPosition(pos);
const int ancLine = lineForPosition(anc);
if (anc < pos) {
pos = lastPositionInLine(posLine);
anc = firstPositionInLine(ancLine);
} else {
pos = firstPositionInLine(posLine);
anc = lastPositionInLine(ancLine);
}
setAnchorAndPosition(anc, pos);
} else if (m_visualMode == VisualCharMode) {
/* Nothing */
} else {
QTC_CHECK(false);
}
2010-09-14 14:04:13 +02:00
} else {
if (m_subsubmode == SearchSubSubMode && !m_searchCursor.isNull())
setCursor(m_searchCursor);
else
setAnchorAndPosition(pos, pos);
}
2010-09-14 14:04:13 +02:00
m_oldExternalPosition = position();
m_oldExternalAnchor = anchor();
m_oldVisualMode = m_visualMode;
}
void FakeVimHandler::Private::importSelection()
{
2010-09-13 15:23:20 +02:00
bool hasBlock = false;
emit q->requestHasBlockSelection(&hasBlock);
2010-09-14 14:04:13 +02:00
if (position() == m_oldExternalPosition
&& anchor() == m_oldExternalAnchor) {
// Undo drawing correction.
m_visualMode = m_oldVisualMode;
setAnchorAndPosition(m_oldInternalAnchor, m_oldInternalPosition);
//setMark('<', m_oldInternalAnchor);
//setMark('>', m_oldInternalPosition);
} else {
// Import new selection.
Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
if (cursor().hasSelection()) {
if (mods & HostOsInfo::controlModifier())
2010-09-14 14:04:13 +02:00
m_visualMode = VisualBlockMode;
else if (mods & Qt::AltModifier)
m_visualMode = VisualBlockMode;
else if (mods & Qt::ShiftModifier)
m_visualMode = VisualLineMode;
else
m_visualMode = VisualCharMode;
} else {
m_visualMode = NoVisualMode;
}
//dump("IS @");
//setMark('<', tc.anchor());
//setMark('>', tc.position());
}
}
2010-03-09 16:12:08 +01:00
void FakeVimHandler::Private::updateEditor()
{
const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
EDITOR(setTabStopWidth(charWidth * config(ConfigTabStop).toInt()));
2010-04-28 16:19:51 +02:00
setupCharClass();
2010-03-09 16:12:08 +01:00
}
void FakeVimHandler::Private::restoreWidget(int tabSize)
{
//clearMessage();
//updateMiniBuffer();
//EDITOR(removeEventFilter(q));
//EDITOR(setReadOnly(m_wasReadOnly));
const int charWidth = QFontMetrics(EDITOR(font())).width(QChar(' '));
EDITOR(setTabStopWidth(charWidth * tabSize));
m_visualMode = NoVisualMode;
// Force "ordinary" cursor.
m_mode = InsertMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
2010-09-13 15:23:20 +02:00
updateCursorShape();
updateSelection();
updateHighlights();
}
2010-03-26 13:22:06 +01:00
EventResult FakeVimHandler::Private::handleKey(const Input &input)
{
KEY_DEBUG("HANDLE INPUT: " << input << " MODE: " << mode);
bool handleMapped = true;
bool hasInput = input.isValid();
if (hasInput)
g.pendingInput.append(input);
// Waiting on input to complete mapping?
if (g.inputTimer != -1) {
killTimer(g.inputTimer);
g.inputTimer = -1;
// If there is a new input add it to incomplete input or
// if the mapped input can be completed complete.
if (hasInput && g.currentMap.walk(input)) {
if (g.currentMap.canExtend()) {
g.inputTimer = startTimer(1000);
return EventHandled;
} else {
hasInput = false;
handleMappedKeys();
}
} else if (g.currentMap.isComplete()) {
handleMappedKeys();
} else {
g.currentMap.reset();
handleMapped = false;
}
}
EventResult r = EventUnhandled;
while (!g.pendingInput.isEmpty()) {
const Input &in = g.pendingInput.front();
// invalid input is used to pop mapping state
if (!in.isValid()) {
g.mapStates.pop_back();
QTC_CHECK(!g.mapStates.empty());
endEditBlock();
if (g.mapStates.size() == 1)
g.commandBuffer.setHistoryAutoSave(true);
if (m_mode == ExMode || m_subsubmode == SearchSubSubMode)
updateMiniBuffer(); // update cursor position on command line
} else {
if (handleMapped && !g.mapStates.last().noremap && m_subsubmode != SearchSubSubMode) {
if (!g.currentMap.isValid()) {
g.currentMap.reset(currentModeCode());
if (!g.currentMap.walk(g.pendingInput) && g.currentMap.isComplete()) {
handleMappedKeys();
continue;
}
}
// handle user mapping
if (g.currentMap.canExtend()) {
// wait for user to press any key or trigger complete mapping after interval
g.inputTimer = startTimer(1000);
return EventHandled;
} else if (g.currentMap.isComplete()) {
handleMappedKeys();
continue;
}
}
r = handleDefaultKey(in);
// TODO: Unhadled events!
}
handleMapped = true;
g.pendingInput.pop_front();
2010-03-26 13:22:06 +01:00
}
return r;
}
EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input)
{
if (input == Nop)
return EventHandled;
else if (m_subsubmode == SearchSubSubMode)
return handleSearchSubSubMode(input);
else if (m_mode == CommandMode)
return handleCommandMode(input);
else if (m_mode == InsertMode)
return handleInsertMode(input);
else if (m_mode == ReplaceMode)
return handleReplaceMode(input);
else if (m_mode == ExMode)
return handleExMode(input);
return EventUnhandled;
}
void FakeVimHandler::Private::handleMappedKeys()
2010-03-26 13:22:06 +01:00
{
int maxMapDepth = g.mapStates.last().maxMapDepth - 1;
int invalidCount = g.currentMap.invalidInputCount();
if (invalidCount > 0) {
g.mapStates.remove(g.mapStates.size() - invalidCount, invalidCount);
QTC_CHECK(!g.mapStates.empty());
for (int i = 0; i < invalidCount; ++i)
endEditBlock();
2010-03-26 13:22:06 +01:00
}
if (maxMapDepth <= 0) {
showMessage(MessageError, "recursive mapping");
g.pendingInput.remove(0, g.currentMap.mapLength() + invalidCount);
} else {
const Inputs &inputs = g.currentMap.inputs();
QVector<Input> rest = g.pendingInput.mid(g.currentMap.mapLength() + invalidCount);
g.pendingInput.clear();
g.pendingInput << inputs << Input() << rest;
g.mapStates << MappingState(maxMapDepth, inputs.noremap(), inputs.silent());
g.commandBuffer.setHistoryAutoSave(false);
beginEditBlock();
}
g.currentMap.reset();
2010-03-26 13:22:06 +01:00
}
void FakeVimHandler::Private::timerEvent(QTimerEvent *ev)
{
if (ev->timerId() == g.inputTimer) {
if (g.currentMap.isComplete())
handleMappedKeys();
handleKey(Input());
}
2010-03-26 13:22:06 +01:00
}
void FakeVimHandler::Private::stopIncrementalFind()
{
if (m_findPending) {
m_findPending = false;
2010-09-14 14:04:13 +02:00
QTextCursor tc = cursor();
setAnchorAndPosition(m_findStartPosition, tc.selectionStart());
finishMovement();
setAnchor();
}
}
void FakeVimHandler::Private::updateFind(bool isComplete)
{
if (!isComplete && !hasConfig(ConfigIncSearch))
return;
g.currentMessage.clear();
const QString &needle = g.searchBuffer.contents();
SearchData sd;
sd.needle = needle;
sd.forward = m_lastSearchForward;
sd.highlightMatches = isComplete;
search(sd, isComplete);
}
bool FakeVimHandler::Private::atEmptyLine(const QTextCursor &tc) const
{
if (tc.isNull())
return atEmptyLine(cursor());
return tc.block().length() == 1;
}
bool FakeVimHandler::Private::atBoundary(bool end, bool simple, bool onlyWords,
const QTextCursor &tc) const
{
if (tc.isNull())
return atBoundary(end, simple, onlyWords, cursor());
if (atEmptyLine(tc))
return true;
int pos = tc.position();
QChar c1 = document()->characterAt(pos);
QChar c2 = document()->characterAt(pos + (end ? 1 : -1));
int thisClass = charClass(c1, simple);
return (!onlyWords || thisClass != 0)
&& (c2.isNull() || c2 == ParagraphSeparator || thisClass != charClass(c2, simple));
}
bool FakeVimHandler::Private::atWordBoundary(bool end, bool simple, const QTextCursor &tc) const
{
return atBoundary(end, simple, true, tc);
}
bool FakeVimHandler::Private::atWordStart(bool simple, const QTextCursor &tc) const
{
return atWordBoundary(false, simple, tc);
}
bool FakeVimHandler::Private::atWordEnd(bool simple, const QTextCursor &tc) const
{
return atWordBoundary(true, simple, tc);
}
bool FakeVimHandler::Private::isFirstNonBlankOnLine(int pos)
{
for (int i = document()->findBlock(pos).position(); i < pos; ++i) {
if (!document()->characterAt(i).isSpace())
return false;
}
return true;
}
void FakeVimHandler::Private::setUndoPosition(bool overwrite)
{
const int rev = revision();
if (!overwrite && !m_undo.empty() && m_undo.top().revision >= rev)
return;
int pos = position();
if (m_mode != InsertMode && m_mode != ReplaceMode) {
if (isVisualMode() || m_submode == DeleteSubMode) {
pos = qMin(pos, anchor());
if (isVisualLineMode())
pos = firstPositionInLine(lineForPosition(pos));
} else if (m_movetype == MoveLineWise && hasConfig(ConfigStartOfLine)) {
QTextCursor tc = cursor();
moveToFirstNonBlankOnLine(&tc);
pos = qMin(pos, tc.position());
}
}
m_redo.clear();
while (!m_undo.empty() && m_undo.top().revision >= rev)
m_undo.pop();
m_undo.push(State(rev, pos, lineForPosition(pos), m_marks));
m_lastChangePosition = pos;
}
2009-04-08 16:05:24 +02:00
void FakeVimHandler::Private::moveDown(int n)
{
2009-04-09 13:48:59 +02:00
#if 0
// does not work for "hidden" documents like in the autotests
2010-09-14 14:04:13 +02:00
tc.movePosition(Down, MoveAnchor, n);
2009-04-09 13:48:59 +02:00
#else
2010-09-13 15:23:20 +02:00
const int col = position() - block().position();
const int lastLine = document()->lastBlock().blockNumber();
2010-09-13 15:23:20 +02:00
const int targetLine = qMax(0, qMin(lastLine, block().blockNumber() + n));
const QTextBlock &block = document()->findBlockByNumber(targetLine);
2009-04-09 15:44:51 +02:00
const int pos = block.position();
2010-09-14 16:58:31 +02:00
setPosition(pos + qMax(0, qMin(block.length() - 2, col)));
2009-04-09 15:44:51 +02:00
moveToTargetColumn();
2009-04-09 13:48:59 +02:00
#endif
2009-04-08 16:05:24 +02:00
}
void FakeVimHandler::Private::moveToEndOfLine()
{
2009-04-09 13:48:59 +02:00
#if 0
// does not work for "hidden" documents like in the autotests
2010-09-14 14:04:13 +02:00
tc.movePosition(EndOfLine, MoveAnchor);
2009-04-09 13:48:59 +02:00
#else
2010-09-13 15:23:20 +02:00
const int pos = block().position() + block().length() - 2;
setPosition(qMax(block().position(), pos));
2009-04-09 13:48:59 +02:00
#endif
2009-04-08 16:05:24 +02:00
}
2009-07-03 11:22:18 +02:00
void FakeVimHandler::Private::moveBehindEndOfLine()
{
2010-09-13 15:23:20 +02:00
int pos = qMin(block().position() + block().length() - 1,
lastPositionInDocument());
2009-07-03 11:22:18 +02:00
setPosition(pos);
}
void FakeVimHandler::Private::moveToStartOfLine()
{
#if 0
// does not work for "hidden" documents like in the autotests
2010-09-14 14:04:13 +02:00
tc.movePosition(StartOfLine, MoveAnchor);
#else
2010-09-13 15:23:20 +02:00
setPosition(block().position());
#endif
}
void FakeVimHandler::Private::finishMovement(const QString &dotCommand, int count)
{
finishMovement(dotCommand.arg(count));
}
2009-01-06 11:43:49 +01:00
void FakeVimHandler::Private::finishMovement(const QString &dotCommand)
{
2010-09-14 14:04:13 +02:00
//dump("FINISH MOVEMENT");
2009-01-08 17:21:51 +01:00
if (m_submode == FilterSubMode) {
2009-01-16 16:15:01 +01:00
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
setPosition(qMin(anchor(), position()));
enterExMode();
g.currentMessage.clear();
g.commandBuffer.setContents(QString(".,+%1!").arg(qAbs(endLine - beginLine)));
2009-01-08 17:21:51 +01:00
updateMiniBuffer();
return;
}
2010-09-14 14:04:13 +02:00
//if (isVisualMode())
// setMark('>', position());
if (m_submode == ChangeSubMode
|| m_submode == DeleteSubMode
|| m_submode == YankSubMode
|| m_submode == TransformSubMode) {
2010-09-14 14:04:13 +02:00
if (m_movetype == MoveExclusive) {
if (anchor() != position() && atBlockStart()) {
// Exlusive motion ending at the beginning of line
// becomes inclusive and end is moved to end of previous line.
m_movetype = MoveInclusive;
moveToStartOfLine();
moveLeft();
// Exclusive motion ending at the beginning of line and
// starting at or before first non-blank on a line becomes linewise.
if (anchor() < block().position() && isFirstNonBlankOnLine(anchor())) {
m_movetype = MoveLineWise;
}
}
}
if (m_submode != YankSubMode)
beginEditBlock();
2010-09-14 14:04:13 +02:00
if (m_movetype == MoveLineWise)
m_rangemode = (m_submode == ChangeSubMode)
? RangeLineModeExclusive
: RangeLineMode;
if (m_movetype == MoveInclusive) {
if (anchor() <= position()) {
if (!atBlockEnd())
2010-09-14 14:04:13 +02:00
setPosition(position() + 1); // correction
// If more than one line is selected and all are selected completely
// movement becomes linewise.
int start = anchor();
if (start < block().position() && isFirstNonBlankOnLine(start) && atBlockEnd()) {
moveRight();
if (atEmptyLine())
moveRight();
m_movetype = MoveLineWise;
}
} else {
2010-09-14 14:04:13 +02:00
setAnchorAndPosition(anchor() + 1, position());
}
}
if (m_positionPastEnd) {
2010-09-14 14:04:13 +02:00
const int anc = anchor();
moveBehindEndOfLine();
moveRight();
2010-09-14 14:04:13 +02:00
setAnchorAndPosition(anc, position());
}
if (m_anchorPastEnd) {
2010-09-14 14:04:13 +02:00
setAnchorAndPosition(anchor() + 1, position());
}
if (m_submode != TransformSubMode) {
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
if (m_movetype == MoveLineWise)
setRegister(m_register, registerContents(m_register), RangeLineMode);
}
m_positionPastEnd = m_anchorPastEnd = false;
}
if (m_submode == ChangeSubMode) {
2010-04-19 11:35:48 +02:00
if (m_rangemode == RangeLineMode)
m_rangemode = RangeLineModeExclusive;
2010-05-12 11:18:18 +02:00
removeText(currentRange());
2009-01-06 11:43:49 +01:00
if (!dotCommand.isEmpty())
setDotCommand(QLatin1Char('c') + dotCommand);
2010-04-19 11:35:48 +02:00
if (m_movetype == MoveLineWise)
insertAutomaticIndentation(true);
endEditBlock();
enterInsertMode();
m_submode = NoSubMode;
} else if (m_submode == DeleteSubMode) {
setUndoPosition();
Range range = currentRange();
removeText(range);
2009-01-06 11:43:49 +01:00
if (!dotCommand.isEmpty())
setDotCommand(QLatin1Char('d') + dotCommand);
if (m_movetype == MoveLineWise)
handleStartOfLine();
m_submode = NoSubMode;
if (atEndOfLine())
2009-01-16 17:38:15 +01:00
moveLeft();
else
setTargetColumn();
endEditBlock();
2008-12-23 21:34:21 +01:00
} else if (m_submode == YankSubMode) {
m_submode = NoSubMode;
const int la = lineForPosition(anchor());
const int lp = lineForPosition(position());
if (m_register != '"') {
2010-05-11 14:26:37 +02:00
setPosition(mark(m_register));
moveToStartOfLine();
} else {
if (anchor() <= position())
setPosition(anchor());
}
if (la != lp)
showMessage(MessageInfo, QString("%1 lines yanked").arg(qAbs(la - lp) + 1));
} else if (m_submode == TransformSubMode) {
if (m_subsubmode == InvertCaseSubSubMode) {
2010-05-12 11:18:18 +02:00
invertCase(currentRange());
if (!dotCommand.isEmpty())
setDotCommand(QLatin1Char('~') + dotCommand);
} else if (m_subsubmode == UpCaseSubSubMode) {
2010-05-12 11:18:18 +02:00
upCase(currentRange());
if (!dotCommand.isEmpty())
setDotCommand("gU" + dotCommand);
} else if (m_subsubmode == DownCaseSubSubMode) {
2010-05-12 11:18:18 +02:00
downCase(currentRange());
if (!dotCommand.isEmpty())
setDotCommand("gu" + dotCommand);
}
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
setPosition(qMin(anchor(), position()));
if (m_movetype == MoveLineWise)
handleStartOfLine();
endEditBlock();
} else if (m_submode == IndentSubMode) {
recordJump();
setUndoPosition();
indentSelectedText();
m_submode = NoSubMode;
} else if (m_submode == ShiftRightSubMode) {
recordJump();
setUndoPosition();
shiftRegionRight(1);
m_submode = NoSubMode;
} else if (m_submode == ShiftLeftSubMode) {
recordJump();
setUndoPosition();
shiftRegionLeft(1);
m_submode = NoSubMode;
}
resetCommandMode();
}
void FakeVimHandler::Private::resetCommandMode()
{
m_movetype = MoveInclusive;
2008-12-25 22:41:09 +01:00
m_mvcount.clear();
m_opcount.clear();
2008-12-27 13:39:34 +01:00
m_gflag = false;
m_register = '"';
2010-09-13 15:23:20 +02:00
//m_tc.clearSelection();
m_rangemode = RangeCharMode;
}
void FakeVimHandler::Private::updateSelection()
{
QList<QTextEdit::ExtraSelection> selections = m_extraSelections;
if (hasConfig(ConfigShowMarks)) {
for (MarksIterator it(m_marks); it.hasNext(); ) {
it.next();
QTextEdit::ExtraSelection sel;
const int pos = it.value().position();
2010-09-13 15:23:20 +02:00
sel.cursor = cursor();
sel.cursor.setPosition(pos, MoveAnchor);
sel.cursor.setPosition(pos + 1, KeepAnchor);
2010-09-13 15:23:20 +02:00
sel.format = cursor().blockCharFormat();
sel.format.setForeground(Qt::blue);
sel.format.setBackground(Qt::green);
selections.append(sel);
}
}
2010-09-14 14:04:13 +02:00
//qDebug() << "SELECTION: " << selections;
emit q->selectionChanged(selections);
}
void FakeVimHandler::Private::updateHighlights()
{
if (!hasConfig(ConfigUseCoreSearch))
emit q->highlightMatches(m_oldNeedle);
}
2008-12-27 21:01:05 +01:00
void FakeVimHandler::Private::updateMiniBuffer()
{
if (!m_textedit && !m_plaintextedit)
return;
2008-12-27 21:01:05 +01:00
QString msg;
int cursorPos = -1;
MessageLevel messageLevel = MessageMode;
if (g.mapStates.last().silent && g.currentMessageLevel < MessageInfo)
g.currentMessage.clear();
if (m_passing) {
msg = "PASSING";
} else if (m_subsubmode == SearchSubSubMode) {
msg = g.searchBuffer.display();
if (g.mapStates.size() == 1)
cursorPos = g.searchBuffer.cursorPos() + 1;
} else if (m_mode == ExMode) {
msg = g.commandBuffer.display();
if (g.mapStates.size() == 1)
cursorPos = g.commandBuffer.cursorPos() + 1;
} else if (!g.currentMessage.isEmpty()) {
msg = g.currentMessage;
g.currentMessage.clear();
messageLevel = g.currentMessageLevel;
} else if (g.mapStates.size() > 1 && !g.mapStates.last().silent) {
// Do not reset previous message when after running a mapped command.
return;
} else if (m_mode == CommandMode && isVisualMode()) {
if (isVisualCharMode()) {
msg = "VISUAL";
} else if (isVisualLineMode()) {
msg = "VISUAL LINE";
} else if (isVisualBlockMode()) {
msg = "VISUAL BLOCK";
2009-01-08 17:21:51 +01:00
}
} else if (m_mode == InsertMode) {
msg = "INSERT";
} else if (m_mode == ReplaceMode) {
msg = "REPLACE";
} else {
QTC_CHECK(m_mode == CommandMode && m_subsubmode != SearchSubSubMode);
msg = "COMMAND";
2008-12-27 22:41:47 +01:00
}
emit q->commandBufferChanged(msg, cursorPos, messageLevel, q);
2009-01-07 18:05:45 +01:00
int linesInDoc = linesInDocument();
int l = cursorLine();
2009-01-07 18:05:45 +01:00
QString status;
2010-05-10 09:01:30 +02:00
const QString pos = QString::fromLatin1("%1,%2")
.arg(l + 1).arg(physicalCursorColumn() + 1);
2008-12-27 21:01:05 +01:00
// FIXME: physical "-" logical
2009-01-06 11:51:03 +01:00
if (linesInDoc != 0) {
2009-10-16 11:30:46 +02:00
status = FakeVimHandler::tr("%1%2%").arg(pos, -10).arg(l * 100 / linesInDoc, 4);
2009-01-06 11:51:03 +01:00
} else {
2009-10-16 11:30:46 +02:00
status = FakeVimHandler::tr("%1All").arg(pos, -10);
2009-01-06 11:51:03 +01:00
}
2009-01-07 18:05:45 +01:00
emit q->statusDataChanged(status);
}
void FakeVimHandler::Private::showMessage(MessageLevel level, const QString &msg)
2009-01-08 17:40:27 +01:00
{
//qDebug() << "MSG: " << msg;
g.currentMessage = msg;
g.currentMessageLevel = level;
2009-01-08 17:40:27 +01:00
updateMiniBuffer();
}
void FakeVimHandler::Private::notImplementedYet()
{
qDebug() << "Not implemented in FakeVim";
showMessage(MessageError, FakeVimHandler::tr("Not implemented in FakeVim"));
updateMiniBuffer();
}
void FakeVimHandler::Private::passShortcuts(bool enable)
{
m_passing = enable;
updateMiniBuffer();
if (enable)
QCoreApplication::instance()->installEventFilter(q);
else
QCoreApplication::instance()->removeEventFilter(q);
}
static bool subModeCanUseTextObjects(int submode)
{
return submode == DeleteSubMode
|| submode == YankSubMode
|| submode == ChangeSubMode
|| submode == IndentSubMode
|| submode == ShiftLeftSubMode
|| submode == ShiftRightSubMode;
}
2010-03-26 13:22:06 +01:00
EventResult FakeVimHandler::Private::handleCommandSubSubMode(const Input &input)
{
//const int key = input.key;
EventResult handled = EventHandled;
if (m_subsubmode == FtSubSubMode) {
m_semicolonType = m_subsubdata;
m_semicolonKey = input.text();
bool valid = handleFfTt(m_semicolonKey);
m_subsubmode = NoSubSubMode;
if (!valid) {
m_submode = NoSubMode;
finishMovement();
} else {
finishMovement(QString("%1%2%3")
.arg(count())
.arg(m_semicolonType.text())
.arg(m_semicolonKey));
}
} else if (m_subsubmode == TextObjectSubSubMode) {
if (input.is('w'))
selectWordTextObject(m_subsubdata.is('i'));
else if (input.is('W'))
selectWORDTextObject(m_subsubdata.is('i'));
else if (input.is('s'))
selectSentenceTextObject(m_subsubdata.is('i'));
else if (input.is('p'))
selectParagraphTextObject(m_subsubdata.is('i'));
else if (input.is('[') || input.is(']'))
selectBlockTextObject(m_subsubdata.is('i'), '[', ']');
else if (input.is('(') || input.is(')') || input.is('b'))
selectBlockTextObject(m_subsubdata.is('i'), '(', ')');
else if (input.is('<') || input.is('>'))
selectBlockTextObject(m_subsubdata.is('i'), '<', '>');
else if (input.is('{') || input.is('}') || input.is('B'))
selectBlockTextObject(m_subsubdata.is('i'), '{', '}');
else if (input.is('"') || input.is('\'') || input.is('`'))
selectQuotedStringTextObject(m_subsubdata.is('i'), input.asChar());
m_subsubmode = NoSubSubMode;
finishMovement(QString("%1%2%3")
.arg(count())
.arg(m_subsubdata.text())
.arg(input.text()));
} else if (m_subsubmode == MarkSubSubMode) {
2010-09-13 15:23:20 +02:00
setMark(input.asChar().unicode(), position());
m_subsubmode = NoSubSubMode;
} else if (m_subsubmode == BackTickSubSubMode
|| m_subsubmode == TickSubSubMode) {
ushort markChar = input.asChar().unicode();
int m = mark(markChar);
2010-05-11 14:26:37 +02:00
if (m != -1) {
if (markChar == '\'' && !m_jumpListUndo.isEmpty())
m_jumpListUndo.pop();
recordJump();
2010-05-11 14:26:37 +02:00
setPosition(m);
if (m_subsubmode == TickSubSubMode)
moveToFirstNonBlankOnLine();
finishMovement();
} else {
showMessage(MessageError, msgMarkNotSet(input.text()));
}
m_subsubmode = NoSubSubMode;
} else {
handled = EventUnhandled;
}
return handled;
}
2010-07-14 13:02:17 +02:00
EventResult FakeVimHandler::Private::handleOpenSquareSubMode(const Input &input)
{
EventResult handled = EventHandled;
m_submode = NoSubMode;
if (input.is('{')) {
searchBalanced(false, '{', '}');
2010-07-14 13:21:35 +02:00
} else if (input.is('(')) {
searchBalanced(false, '(', ')');
2010-07-14 13:02:17 +02:00
} else {
handled = EventUnhandled;
}
return handled;
}
EventResult FakeVimHandler::Private::handleCloseSquareSubMode(const Input &input)
{
EventResult handled = EventHandled;
m_submode = NoSubMode;
if (input.is('}')) {
searchBalanced(true, '}', '{');
2010-07-14 13:21:35 +02:00
} else if (input.is(')')) {
searchBalanced(true, ')', '(');
2010-07-14 13:02:17 +02:00
} else {
handled = EventUnhandled;
}
return handled;
}
2010-03-26 13:22:06 +01:00
EventResult FakeVimHandler::Private::handleCommandMode(const Input &input)
{
EventResult handled = EventHandled;
if (input.isEscape()) {
if (isVisualMode()) {
leaveVisualMode();
} else if (m_submode != NoSubMode) {
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
finishMovement();
} else {
resetCommandMode();
updateMiniBuffer();
}
} else if (m_subsubmode != NoSubSubMode) {
2010-03-26 13:22:06 +01:00
handleCommandSubSubMode(input);
2010-07-14 13:02:17 +02:00
} else if (m_submode == OpenSquareSubMode) {
handled = handleOpenSquareSubMode(input);
} else if (m_submode == CloseSquareSubMode) {
handled = handleCloseSquareSubMode(input);
} else if (m_submode == WindowSubMode) {
emit q->windowCommandRequested(input.key());
2009-04-03 16:33:28 +02:00
m_submode = NoSubMode;
} else if (m_submode == RegisterSubMode) {
2010-05-20 14:08:11 +02:00
m_register = input.asChar().unicode();
m_submode = NoSubMode;
m_rangemode = RangeLineMode;
2010-05-28 13:09:24 +02:00
} else if (m_submode == ReplaceSubMode) {
if (isVisualMode()) {
setUndoPosition();
2010-05-28 13:09:24 +02:00
if (isVisualLineMode())
m_rangemode = RangeLineMode;
else if (isVisualBlockMode())
m_rangemode = RangeBlockMode;
else
m_rangemode = RangeCharMode;
leaveVisualMode();
Range range = currentRange();
Transformation tr =
&FakeVimHandler::Private::replaceByCharTransform;
2010-05-28 13:09:24 +02:00
transformText(range, tr, input.asChar());
setPosition(range.beginPos);
} else if (count() <= rightDist()) {
setUndoPosition();
2010-05-28 13:09:24 +02:00
setAnchor();
moveRight(count());
Range range = currentRange();
if (input.isReturn()) {
beginEditBlock();
replaceText(range, QString());
insertText(QString("\n"));
endEditBlock();
} else {
replaceText(range, QString(count(), input.asChar()));
moveLeft();
}
setTargetColumn();
2010-05-28 13:09:24 +02:00
setDotCommand("%1r" + input.text(), count());
}
m_submode = NoSubMode;
finishMovement();
} else if (m_submode == ChangeSubMode && input.is('c')) { // tested
m_movetype = MoveLineWise;
setUndoPosition();
moveToStartOfLine();
setAnchor();
moveDown(count() - 1);
moveToEndOfLine();
m_lastInsertion.clear();
setDotCommand("%1cc", count());
finishMovement();
} else if (m_submode == DeleteSubMode && input.is('d')) { // tested
m_movetype = MoveLineWise;
setUndoPosition();
int endPos = firstPositionInLine(lineForPosition(position()) + count() - 1);
Range range(position(), endPos, RangeLineMode);
yankText(range);
removeText(range);
setDotCommand("%1dd", count());
m_submode = NoSubMode;
handleStartOfLine();
2009-10-16 11:30:46 +02:00
setTargetColumn();
finishMovement();
2010-04-19 11:35:48 +02:00
} else if ((subModeCanUseTextObjects(m_submode) || isVisualMode())
&& (input.is('a') || input.is('i'))) {
m_subsubmode = TextObjectSubSubMode;
m_subsubdata = input;
} else if (m_submode == ShiftLeftSubMode && input.is('<')) {
m_movetype = MoveLineWise;
setUndoPosition();
setAnchor();
moveDown(count() - 1);
setDotCommand("%1<<", count());
finishMovement();
} else if (m_submode == ShiftRightSubMode && input.is('>')) {
m_movetype = MoveLineWise;
setUndoPosition();
setAnchor();
moveDown(count() - 1);
setDotCommand("%1>>", count());
finishMovement();
} else if (m_submode == IndentSubMode && input.is('=')) {
m_movetype = MoveLineWise;
setUndoPosition();
2009-03-06 13:03:33 +01:00
setAnchor();
moveDown(count() - 1);
2009-05-05 08:46:24 +02:00
setDotCommand("%1==", count());
finishMovement();
2008-12-19 16:20:39 +01:00
} else if (m_submode == ZSubMode) {
//qDebug() << "Z_MODE " << cursorLine() << linesOnScreen();
if (input.isReturn() || input.is('t')) {
// Cursor line to top of window.
2009-01-27 12:44:42 +01:00
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
2009-03-20 08:44:52 +01:00
scrollUp(- cursorLineOnScreen());
if (input.isReturn())
2009-01-27 12:44:42 +01:00
moveToFirstNonBlankOnLine();
2008-12-19 16:20:39 +01:00
finishMovement();
} else if (input.is('.') || input.is('z')) {
// Cursor line to center of window.
2009-01-27 12:44:42 +01:00
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
2009-03-20 08:44:52 +01:00
scrollUp(linesOnScreen() / 2 - cursorLineOnScreen());
if (input.is('.'))
2009-01-27 12:44:42 +01:00
moveToFirstNonBlankOnLine();
2009-01-27 12:11:08 +01:00
finishMovement();
} else if (input.is('-') || input.is('b')) {
// Cursor line to bottom of window.
2009-01-27 12:44:42 +01:00
if (!m_mvcount.isEmpty())
setPosition(firstPositionInLine(count()));
2009-03-20 08:44:52 +01:00
scrollUp(linesOnScreen() - cursorLineOnScreen());
if (input.is('-'))
2009-01-27 12:44:42 +01:00
moveToFirstNonBlankOnLine();
2009-01-27 12:29:14 +01:00
finishMovement();
2008-12-25 13:20:09 +01:00
} else {
qDebug() << "IGNORED Z_MODE " << input.key() << input.text();
2008-12-19 16:20:39 +01:00
}
m_submode = NoSubMode;
} else if (m_submode == CapitalZSubMode) {
// Recognize ZZ and ZQ as aliases for ":x" and ":q!".
m_submode = NoSubMode;
if (input.is('Z'))
handleExCommand(QString(QLatin1Char('x')));
else if (input.is('Q'))
handleExCommand("q!");
} else if (input.isDigit()) {
if (input.is('0') && m_mvcount.isEmpty()) {
m_movetype = MoveExclusive;
moveToStartOfLine();
setTargetColumn();
finishMovement(QString(QLatin1Char('0')));
} else {
m_mvcount.append(input.text());
}
} else {
2011-05-16 17:21:43 +02:00
handled = handleCommandMode1(input);
}
return handled;
}
2011-05-16 17:21:43 +02:00
EventResult FakeVimHandler::Private::handleCommandMode1(const Input &input)
{
EventResult handled = EventHandled;
if (input.is('^') || input.is('_')) {
moveToFirstNonBlankOnLine();
setTargetColumn();
m_movetype = MoveExclusive;
finishMovement(input.text());
} else if (0 && input.is(',')) {
// 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 (input.is(';')) {
m_subsubmode = FtSubSubMode;
m_subsubdata = m_semicolonType;
handleFfTt(m_semicolonKey);
m_subsubmode = NoSubSubMode;
finishMovement();
} else if (input.is('&')) {
handleExCommand(m_gflag ? "%s//~/&" : "s");
} else if (input.is(':')) {
enterExMode();
g.currentMessage.clear();
g.commandBuffer.clear();
if (isVisualMode())
g.commandBuffer.setContents("'<,'>");
updateMiniBuffer();
} else if (input.is('/') || input.is('?')) {
m_lastSearchForward = input.is('/');
if (hasConfig(ConfigUseCoreSearch)) {
// re-use the core dialog.
m_findPending = true;
m_findStartPosition = position();
m_movetype = MoveExclusive;
setAnchor(); // clear selection: otherwise, search is restricted to selection
emit q->findRequested(!m_lastSearchForward);
} else {
// FIXME: make core find dialog sufficiently flexible to
// produce the "default vi" behaviour too. For now, roll our own.
g.currentMessage.clear();
m_movetype = MoveExclusive;
m_subsubmode = SearchSubSubMode;
g.searchBuffer.setPrompt(m_lastSearchForward ? '/' : '?');
m_searchStartPosition = position();
m_searchFromScreenLine = firstVisibleLine();
m_searchCursor = QTextCursor();
g.searchBuffer.clear();
updateMiniBuffer();
}
} else if (input.is('`')) {
m_subsubmode = BackTickSubSubMode;
if (m_submode != NoSubMode)
m_movetype = MoveLineWise;
} else if (input.is('#') || input.is('*')) {
2009-01-16 09:56:08 +01:00
// FIXME: That's not proper vim behaviour
2010-09-13 15:23:20 +02:00
QString needle;
2010-09-14 16:58:31 +02:00
QTextCursor tc = cursor();
tc.select(QTextCursor::WordUnderCursor);
needle = "\\<" + tc.selection().toPlainText() + "\\>";
2010-09-16 14:32:29 +02:00
setAnchorAndPosition(tc.position(), tc.anchor());
g.searchBuffer.historyPush(needle);
m_lastSearch = needle;
m_lastSearchForward = input.is('*');
searchNext();
finishMovement();
} else if (input.is('\'')) {
m_subsubmode = TickSubSubMode;
if (m_submode != NoSubMode)
m_movetype = MoveLineWise;
} else if (input.is('|')) {
2009-01-16 16:15:01 +01:00
moveToStartOfLine();
moveRight(qMin(count(), rightDist()) - 1);
setTargetColumn();
finishMovement();
} else if (input.is('!') && isNoVisualMode()) {
2009-01-08 17:21:51 +01:00
m_submode = FilterSubMode;
} else if (input.is('!') && isVisualMode()) {
enterExMode();
g.currentMessage.clear();
g.commandBuffer.setContents("'<,'>!");
2009-01-08 17:21:51 +01:00
updateMiniBuffer();
} else if (input.is('"')) {
m_submode = RegisterSubMode;
} else if (input.isReturn()) {
2009-01-16 16:15:01 +01:00
moveToStartOfLine();
moveDown();
moveToFirstNonBlankOnLine();
m_movetype = MoveLineWise;
finishMovement("%1j", count());
} else if (input.is('-')) {
moveToStartOfLine();
moveUp(count());
moveToFirstNonBlankOnLine();
m_movetype = MoveLineWise;
finishMovement("%1-", count());
} else if (input.is('+')) {
moveToStartOfLine();
moveDown(count());
moveToFirstNonBlankOnLine();
m_movetype = MoveLineWise;
finishMovement("%1+", count());
} else if (input.isKey(Key_Home)) {
2009-01-16 16:15:01 +01:00
moveToStartOfLine();
setTargetColumn();
finishMovement();
} else if (input.is('$') || input.isKey(Key_End)) {
if (count() > 1)
moveDown(count() - 1);
2009-01-16 16:15:01 +01:00
moveToEndOfLine();
m_movetype = MoveInclusive;
setTargetColumn();
if (m_submode == NoSubMode)
m_targetColumn = -1;
if (isVisualMode())
m_visualTargetColumn = -1;
finishMovement("%1$", count());
} else if (input.is(',')) {
passShortcuts(true);
} else if (input.is('.')) {
2010-05-05 15:50:05 +02:00
//qDebug() << "REPEATING" << quoteUnprintable(g.dotCommand) << count()
// << input;
2010-05-05 15:50:05 +02:00
QString savedCommand = g.dotCommand;
g.dotCommand.clear();
2009-04-08 16:05:24 +02:00
replay(savedCommand, count());
2009-03-05 14:08:42 +01:00
enterCommandMode();
2010-05-05 15:50:05 +02:00
g.dotCommand = savedCommand;
} else if (input.is('<')) {
if (isNoVisualMode()) {
m_submode = ShiftLeftSubMode;
} else {
shiftRegionLeft(count());
leaveVisualMode();
}
} else if (input.is('>')) {
if (isNoVisualMode()) {
m_submode = ShiftRightSubMode;
} else {
shiftRegionRight(count());
leaveVisualMode();
}
} else if (input.is('=')) {
if (isNoVisualMode()) {
m_submode = IndentSubMode;
} else {
indentSelectedText();
leaveVisualMode();
}
} else if (input.is('%')) {
moveToMatchingParanthesis();
finishMovement();
} else if ((!isVisualMode() && input.is('a')) || (isVisualMode() && input.is('A'))) {
leaveVisualMode();
setUndoPosition();
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterInsertMode();
2008-12-27 21:51:06 +01:00
m_lastInsertion.clear();
if (!atEndOfLine())
moveRight();
updateMiniBuffer();
} else if (input.is('A')) {
2010-05-20 16:32:54 +02:00
breakEditBlock();
moveBehindEndOfLine();
setUndoPosition();
2010-09-14 18:48:04 +02:00
setAnchor();
2010-09-14 18:30:49 +02:00
enterInsertMode();
setDotCommand(QString(QLatin1Char('A')));
2008-12-27 21:51:06 +01:00
m_lastInsertion.clear();
updateMiniBuffer();
} else if (input.isControl('a')) {
changeNumberTextObject(true);
} else if (input.is('b') || input.isShift(Key_Left)) {
m_movetype = MoveExclusive;
moveToNextWordStart(count(), false, false);
setTargetColumn();
2008-12-25 19:50:14 +01:00
finishMovement();
} else if (input.is('B')) {
m_movetype = MoveExclusive;
moveToNextWordStart(count(), true, false);
setTargetColumn();
2008-12-25 19:50:14 +01:00
finishMovement();
} else if (input.is('c') && isNoVisualMode()) {
setUndoPosition();
if (atEndOfLine())
moveLeft();
2009-01-16 16:15:01 +01:00
setAnchor();
m_submode = ChangeSubMode;
} else if ((input.is('c') || input.is('C') || input.is('s') || input.is('R'))
&& (isVisualCharMode() || isVisualLineMode())) {
if ((input.is('c')|| input.is('s')) && isVisualCharMode()) {
leaveVisualMode();
m_rangemode = RangeCharMode;
} else {
leaveVisualMode();
m_rangemode = RangeLineMode;
// leaveVisualMode() has set this to MoveInclusive for visual character mode
m_movetype = MoveLineWise;
}
m_submode = ChangeSubMode;
finishMovement();
} else if (input.is('C')) {
2009-01-16 16:15:01 +01:00
setAnchor();
moveToEndOfLine();
m_submode = ChangeSubMode;
setDotCommand(QString(QLatin1Char('C')));
finishMovement();
} else if (input.isControl('c')) {
if (isNoVisualMode())
showMessage(MessageInfo, "Type Alt-v,Alt-v to quit FakeVim mode");
else
leaveVisualMode();
} else if (input.is('d') && isNoVisualMode()) {
if (m_rangemode == RangeLineMode) {
2010-09-13 15:23:20 +02:00
int pos = position();
moveToEndOfLine();
setAnchor();
setPosition(pos);
} else {
setAnchor();
}
2009-01-06 11:43:49 +01:00
m_opcount = m_mvcount;
m_mvcount.clear();
m_submode = DeleteSubMode;
} else if ((input.is('d') || input.is('x') || input.isKey(Key_Delete))
&& isVisualMode()) {
setUndoPosition();
if (isVisualCharMode()) {
leaveVisualMode();
m_submode = DeleteSubMode;
finishMovement();
} else if (isVisualLineMode()) {
leaveVisualMode();
m_rangemode = RangeLineMode;
yankText(currentRange(), m_register);
removeText(currentRange());
handleStartOfLine();
} else if (isVisualBlockMode()) {
leaveVisualMode();
m_rangemode = RangeBlockMode;
yankText(currentRange(), m_register);
removeText(currentRange());
setPosition(qMin(position(), anchor()));
}
} else if (input.is('D') && isNoVisualMode()) {
setUndoPosition();
if (atEndOfLine())
moveLeft();
m_submode = DeleteSubMode;
2010-09-14 16:58:31 +02:00
setAnchor();
2009-01-16 16:15:01 +01:00
moveDown(qMax(count() - 1, 0));
m_movetype = MoveInclusive;
2009-02-16 02:13:33 +01:00
moveToEndOfLine();
setDotCommand(QString(QLatin1Char('D')));
finishMovement();
} else if ((input.is('D') || input.is('X')) &&
(isVisualCharMode() || isVisualLineMode())) {
leaveVisualMode();
m_rangemode = RangeLineMode;
m_submode = NoSubMode;
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
removeText(currentRange());
moveToFirstNonBlankOnLine();
} else if ((input.is('D') || input.is('X')) && isVisualBlockMode()) {
leaveVisualMode();
m_rangemode = RangeBlockAndTailMode;
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
removeText(currentRange());
setPosition(qMin(position(), anchor()));
} else if (input.isControl('d')) {
int sline = cursorLineOnScreen();
// FIXME: this should use the "scroll" option, and "count"
moveDown(linesOnScreen() / 2);
handleStartOfLine();
scrollToLine(cursorLine() - sline);
finishMovement();
} else if (input.is('e') && m_gflag) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), false, false);
setTargetColumn();
finishMovement("%1ge", count());
} else if (input.is('e') || input.isShift(Key_Right)) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), false, true, false);
setTargetColumn();
finishMovement("%1e", count());
} else if (input.is('E') && m_gflag) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), true, false);
setTargetColumn();
finishMovement("%1gE", count());
} else if (input.is('E')) {
m_movetype = MoveInclusive;
moveToNextWordEnd(count(), true, true, false);
setTargetColumn();
finishMovement("%1E", count());
} else if (input.isControl('e')) {
2009-03-20 08:44:52 +01:00
// FIXME: this should use the "scroll" option, and "count"
if (cursorLineOnScreen() == 0)
moveDown(1);
scrollDown(1);
finishMovement();
} else if (input.is('f')) {
2008-12-26 00:18:03 +01:00
m_subsubmode = FtSubSubMode;
m_movetype = MoveInclusive;
m_subsubdata = input;
} else if (input.is('F')) {
m_subsubmode = FtSubSubMode;
m_movetype = MoveExclusive;
m_subsubdata = input;
} else if (input.is('g') && !m_gflag) {
m_gflag = true;
} else if (input.is('g') || input.is('G')) {
QString dotCommand = QString("%1G").arg(count());
if (input.is('G') && m_mvcount.isEmpty())
dotCommand = QString(QLatin1Char('G'));
if (input.is('g'))
m_gflag = false;
int n = (input.is('g')) ? 1 : linesInDocument();
n = m_mvcount.isEmpty() ? n : count();
if (m_submode == NoSubMode || m_submode == ZSubMode
|| m_submode == CapitalZSubMode || m_submode == RegisterSubMode) {
2010-09-13 15:23:20 +02:00
setPosition(firstPositionInLine(n));
handleStartOfLine();
} else {
m_movetype = MoveLineWise;
m_rangemode = RangeLineMode;
setAnchor();
2010-09-13 15:23:20 +02:00
setPosition(firstPositionInLine(n));
}
finishMovement(dotCommand);
} else if (input.is('h') || input.isKey(Key_Left) || input.isBackspace()) {
m_movetype = MoveExclusive;
int n = qMin(count(), leftDist());
2010-09-13 15:23:20 +02:00
if (m_fakeEnd && block().length() > 1)
++n;
2009-01-16 16:15:01 +01:00
moveLeft(n);
setTargetColumn();
finishMovement("%1h", count());
} else if (input.is('H')) {
2010-09-14 14:04:13 +02:00
setCursor(EDITOR(cursorForPosition(QPoint(0, 0))));
2009-01-16 16:15:01 +01:00
moveDown(qMax(count() - 1, 0));
handleStartOfLine();
2008-12-19 14:35:57 +01:00
finishMovement();
} else if (!isVisualMode() && (input.is('i') || input.isKey(Key_Insert))) {
setDotCommand(QString(QLatin1Char('i'))); // setDotCommand("%1i", count());
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterInsertMode();
updateMiniBuffer();
if (atEndOfLine())
2009-01-16 17:38:15 +01:00
moveLeft();
2011-05-16 17:21:43 +02:00
} else {
handled = handleCommandMode2(input);
}
return handled;
}
EventResult FakeVimHandler::Private::handleCommandMode2(const Input &input)
{
EventResult handled = EventHandled;
if (input.is('I')) {
setUndoPosition();
setDotCommand(QString(QLatin1Char('I'))); // setDotCommand("%1I", count());
if (isVisualMode()) {
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
m_visualInsertCount = qAbs(endLine - beginLine);
setPosition(qMin(position(), anchor()));
} else {
if (m_gflag)
moveToStartOfLine();
else
moveToFirstNonBlankOnLine();
m_gflag = false;
2010-09-13 15:23:20 +02:00
//m_tc.clearSelection();
}
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterInsertMode();
} else if (input.isControl('i')) {
jump(count());
} else if (input.is('j') || input.isKey(Key_Down)
|| input.isControl('j') || input.isControl('n')) {
m_movetype = MoveLineWise;
moveDown(count());
finishMovement("%1j", count());
} else if (input.is('J')) {
2009-11-19 08:36:21 +01:00
setDotCommand("%1J", count());
beginEditBlock();
2008-12-27 13:39:34 +01:00
if (m_submode == NoSubMode) {
for (int i = qMax(count(), 2) - 1; --i >= 0; ) {
moveBehindEndOfLine();
2010-09-14 16:58:31 +02:00
setAnchor();
moveRight();
2009-12-11 19:09:35 +01:00
if (m_gflag) {
2010-09-14 16:58:31 +02:00
removeText(currentRange());
2009-12-11 19:09:35 +01:00
} else {
while (characterAtCursor() == ' '
|| characterAtCursor() == '\t')
moveRight();
2010-09-14 16:58:31 +02:00
removeText(currentRange());
2010-09-14 14:04:13 +02:00
cursor().insertText(QString(QLatin1Char(' ')));
2009-12-11 19:09:35 +01:00
}
2008-12-27 13:39:34 +01:00
}
2008-12-27 13:50:52 +01:00
if (!m_gflag)
2009-01-16 16:15:01 +01:00
moveLeft();
2008-12-27 13:39:34 +01:00
}
2009-11-19 08:36:21 +01:00
endEditBlock();
finishMovement();
} else if (input.is('k') || input.isKey(Key_Up) || input.isControl('p')) {
m_movetype = MoveLineWise;
moveUp(count());
finishMovement("%1k", count());
} else if (input.is('l') || input.isKey(Key_Right) || input.is(' ')) {
m_movetype = MoveExclusive;
bool pastEnd = count() >= rightDist() - 1;
2010-03-18 16:58:55 +01:00
moveRight(qMax(0, qMin(count(), rightDist() - (m_submode == NoSubMode))));
setTargetColumn();
2010-09-14 14:04:13 +02:00
if (pastEnd && isVisualMode())
m_visualTargetColumn = -1;
finishMovement("%1l", count());
} else if (input.is('L')) {
2010-09-13 15:23:20 +02:00
QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()))));
2010-09-14 14:04:13 +02:00
setCursor(tc);
2009-01-16 16:15:01 +01:00
moveUp(qMax(count(), 1));
handleStartOfLine();
2008-12-19 14:43:14 +01:00
finishMovement();
} else if (input.isControl('l')) {
// screen redraw. should not be needed
} else if (input.is('m')) {
if (m_gflag) {
moveToStartOfLine();
moveRight(qMin(columnsOnScreen() / 2, rightDist()) - 1);
setTargetColumn();
finishMovement();
} else {
m_subsubmode = MarkSubSubMode;
}
} else if (input.is('M')) {
2010-09-13 15:23:20 +02:00
QTextCursor tc = EDITOR(cursorForPosition(QPoint(0, EDITOR(height()) / 2)));
2010-09-14 14:04:13 +02:00
setCursor(tc);
handleStartOfLine();
2008-12-19 15:00:06 +01:00
finishMovement();
} else if (input.is('n') || input.is('N')) {
if (hasConfig(ConfigUseCoreSearch)) {
bool forward = (input.is('n')) ? m_lastSearchForward : !m_lastSearchForward;
int pos = position();
emit q->findNextRequested(!forward);
if (forward && pos == cursor().selectionStart()) {
// if cursor is already positioned at the start of a find result, this is returned
emit q->findNextRequested(false);
}
setPosition(cursor().selectionStart());
} else {
searchNext(input.is('n'));
finishMovement();
}
} else if (isVisualMode() && (input.is('o') || input.is('O'))) {
int pos = position();
2010-09-14 14:04:13 +02:00
setAnchorAndPosition(pos, anchor());
std::swap(m_positionPastEnd, m_anchorPastEnd);
setTargetColumn();
if (m_positionPastEnd)
m_visualTargetColumn = -1;
} else if (input.is('o')) {
setDotCommand("%1o", count());
setUndoPosition();
beginEditBlock();
moveToFirstNonBlankOnLine();
moveBehindEndOfLine();
insertText(QString("\n"));
insertAutomaticIndentation(true);
endEditBlock();
enterInsertMode();
} else if (input.is('O')) {
setDotCommand("%1O", count());
setUndoPosition();
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterInsertMode();
beginEditBlock();
moveToFirstNonBlankOnLine();
moveToStartOfLine();
insertText(QString("\n"));
moveUp();
insertAutomaticIndentation(false);
endEditBlock();
} else if (input.isControl('o')) {
jump(-count());
} else if (input.is('p') || input.is('P')) {
pasteText(input.is('p'));
setTargetColumn();
setDotCommand("%1p", count());
finishMovement();
} else if (input.is('r')) {
2010-05-28 13:09:24 +02:00
m_submode = ReplaceSubMode;
} else if (!isVisualMode() && input.is('R')) {
setUndoPosition();
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterReplaceMode();
updateMiniBuffer();
} else if (input.isControl('r')) {
int repeat = count();
while (--repeat >= 0)
redo();
finishMovement();
} else if (input.is('s') && isVisualBlockMode()) {
setUndoPosition();
Range range(position(), anchor(), RangeBlockMode);
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
m_visualInsertCount = qAbs(endLine - beginLine);
setPosition(qMin(position(), anchor()));
yankText(range, m_register);
removeText(range);
setDotCommand("%1s", count());
breakEditBlock();
enterInsertMode();
} else if (input.is('s')) {
setUndoPosition();
leaveVisualMode();
2009-04-01 15:52:48 +02:00
if (atEndOfLine())
moveLeft();
setAnchor();
2009-01-16 16:15:01 +01:00
moveRight(qMin(count(), rightDist()));
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
removeText(currentRange());
2009-08-20 10:21:35 +02:00
setDotCommand("%1s", count());
m_opcount.clear();
m_mvcount.clear();
2010-05-20 16:32:54 +02:00
breakEditBlock();
enterInsertMode();
} else if (input.is('S')) {
m_movetype = MoveLineWise;
setUndoPosition();
2010-09-14 14:04:13 +02:00
beginEditBlock();
2010-03-18 13:16:32 +01:00
if (!isVisualMode()) {
const int line = cursorLine() + 1;
2010-09-14 14:04:13 +02:00
const int anc = firstPositionInLine(line);
const int pos = lastPositionInLine(line + count() - 1);
setAnchorAndPosition(anc, pos);
}
2010-01-05 17:42:36 +01:00
setDotCommand("%1S", count());
2010-05-20 16:32:54 +02:00
breakEditBlock();
2010-01-05 17:42:36 +01:00
enterInsertMode();
m_submode = ChangeSubMode;
2010-09-14 14:04:13 +02:00
endEditBlock();
finishMovement();
} else if (m_gflag && input.is('t')) {
m_gflag = false;
handleExCommand("tabnext");
} else if (input.is('t')) {
m_movetype = MoveInclusive;
m_subsubmode = FtSubSubMode;
m_subsubdata = input;
} else if (m_gflag && input.is('T')) {
m_gflag = false;
handleExCommand("tabprev");
} else if (input.is('T')) {
m_movetype = MoveExclusive;
2008-12-26 00:18:03 +01:00
m_subsubmode = FtSubSubMode;
m_subsubdata = input;
} else if (input.isControl('t')) {
handleExCommand("pop");
} else if (!m_gflag && input.is('u')) {
int repeat = count();
while (--repeat >= 0)
undo();
finishMovement();
} else if (input.isControl('u')) {
int sline = cursorLineOnScreen();
// FIXME: this should use the "scroll" option, and "count"
moveUp(linesOnScreen() / 2);
handleStartOfLine();
scrollToLine(cursorLine() - sline);
finishMovement();
} else if (m_gflag && input.is('v')) {
if (m_lastSelectionCursor.hasSelection()) {
toggleVisualMode(m_lastSelectionMode);
setCursor(m_lastSelectionCursor);
}
} else if (input.is('v')) {
toggleVisualMode(VisualCharMode);
} else if (input.is('V')) {
toggleVisualMode(VisualLineMode);
} else if (input.isControl('v')) {
toggleVisualMode(VisualBlockMode);
} else if (input.is('w')) { // tested
2009-01-16 16:42:31 +01:00
// Special case: "cw" and "cW" work the same as "ce" and "cE" if the
2010-03-18 16:58:55 +01:00
// cursor is on a non-blank - except if the cursor is on the last
// character of a word: only the current word will be changed
if (m_submode == ChangeSubMode) {
moveToWordEnd(count(), false, true);
m_movetype = MoveInclusive;
} else {
moveToNextWordStart(count(), false, true);
m_movetype = MoveExclusive;
}
setTargetColumn();
finishMovement("%1w", count());
} else if (input.is('W')) {
2009-01-28 18:48:22 +01:00
if (m_submode == ChangeSubMode) {
moveToWordEnd(count(), true, true);
m_movetype = MoveInclusive;
2009-01-28 18:48:22 +01:00
} else {
moveToNextWordStart(count(), true, true);
m_movetype = MoveExclusive;
2009-01-28 18:48:22 +01:00
}
setTargetColumn();
finishMovement("%1W", count());
} else if (input.isControl('w')) {
2009-04-03 16:33:28 +02:00
m_submode = WindowSubMode;
} else if (input.is('x') && isNoVisualMode()) { // = "dl"
m_movetype = MoveExclusive;
m_submode = DeleteSubMode;
2010-09-14 14:04:13 +02:00
const int n = qMin(count(), rightDist());
setAnchorAndPosition(position(), position() + n);
setDotCommand("%1x", count());
2009-03-05 14:08:42 +01:00
finishMovement();
} else if (input.isControl('x')) {
changeNumberTextObject(false);
} else if (input.is('X')) {
if (leftDist() > 0) {
2009-01-16 16:15:01 +01:00
setAnchor();
moveLeft(qMin(count(), leftDist()));
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
removeText(currentRange());
}
finishMovement();
} else if ((m_submode == YankSubMode && input.is('y'))
|| (input.is('Y') && isNoVisualMode())) {
setAnchor();
2009-10-16 11:30:46 +02:00
if (count() > 1)
moveDown(count()-1);
m_rangemode = RangeLineMode;
m_movetype = MoveLineWise;
m_submode = YankSubMode;
finishMovement();
2011-05-13 18:56:25 +02:00
} else if (input.isControl('y')) {
// FIXME: this should use the "scroll" option, and "count"
if (cursorLineOnScreen() == linesOnScreen() - 1)
moveUp(1);
scrollUp(1);
finishMovement();
} else if (input.is('y') && isNoVisualMode()) {
setAnchor();
2008-12-23 21:34:21 +01:00
m_submode = YankSubMode;
} else if (input.is('y') && isVisualCharMode()) {
Range range(position(), anchor(), RangeCharMode);
range.endPos++; // MoveInclusive
yankText(range, m_register);
setPosition(qMin(position(), anchor()));
leaveVisualMode();
finishMovement();
} else if ((input.is('y') && isVisualLineMode())
|| (input.is('Y') && isVisualLineMode())
|| (input.is('Y') && isVisualCharMode())) {
m_rangemode = RangeLineMode;
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
setPosition(qMin(position(), anchor()));
moveToStartOfLine();
leaveVisualMode();
2009-01-16 17:38:15 +01:00
finishMovement();
} else if ((input.is('y') || input.is('Y')) && isVisualBlockMode()) {
m_rangemode = RangeBlockMode;
2010-05-12 11:18:18 +02:00
yankText(currentRange(), m_register);
setPosition(qMin(position(), anchor()));
leaveVisualMode();
finishMovement();
} else if (input.is('z')) {
2008-12-19 16:20:39 +01:00
m_submode = ZSubMode;
} else if (input.is('Z')) {
m_submode = CapitalZSubMode;
} else if (!m_gflag && input.is('~') && !isVisualMode()) {
2010-05-12 12:29:05 +02:00
m_movetype = MoveExclusive;
if (!atEndOfLine()) {
beginEditBlock();
setAnchor();
moveRight(qMin(count(), rightDist()));
if (input.is('~')) {
2010-05-12 11:18:18 +02:00
invertCase(currentRange());
setDotCommand("%1~", count());
} else if (input.is('u')) {
2010-05-12 11:18:18 +02:00
downCase(currentRange());
setDotCommand("%1gu", count());
} else if (input.is('U')) {
2010-05-12 11:18:18 +02:00
upCase(currentRange());
setDotCommand("%1gU", count());
}
endEditBlock();
2008-12-25 22:22:41 +01:00
}
finishMovement();
} else if ((m_gflag && input.is('~') && !isVisualMode())
|| (m_gflag && input.is('u') && !isVisualMode())
|| (m_gflag && input.is('U') && !isVisualMode())) {
m_gflag = false;
2010-05-12 12:29:05 +02:00
m_movetype = MoveExclusive;
if (atEndOfLine())
moveLeft();
setAnchor();
m_submode = TransformSubMode;
if (input.is('~'))
m_subsubmode = InvertCaseSubSubMode;
if (input.is('u'))
m_subsubmode = DownCaseSubSubMode;
else if (input.is('U'))
m_subsubmode = UpCaseSubSubMode;
} else if ((input.is('~') && isVisualMode())
|| (m_gflag && input.is('u') && isVisualMode())
|| (m_gflag && input.is('U') && isVisualMode())) {
m_gflag = false;
2010-05-12 12:29:05 +02:00
m_movetype = MoveExclusive;
if (isVisualLineMode())
m_rangemode = RangeLineMode;
else if (isVisualBlockMode())
m_rangemode = RangeBlockMode;
leaveVisualMode();
m_submode = TransformSubMode;
if (input.is('~'))
m_subsubmode = InvertCaseSubSubMode;
else if (input.is('u'))
m_subsubmode = DownCaseSubSubMode;
else if (input.is('U'))
m_subsubmode = UpCaseSubSubMode;
finishMovement();
2010-07-14 13:02:17 +02:00
} else if (input.is('[')) {
m_submode = OpenSquareSubMode;
} else if (input.is(']')) {
m_submode = CloseSquareSubMode;
} else if (input.isKey(Key_PageDown) || input.isControl('f')) {
moveDown(count() * (linesOnScreen() - 2) - cursorLineOnScreen());
scrollToLine(cursorLine());
handleStartOfLine();
finishMovement();
} else if (input.isKey(Key_PageUp) || input.isControl('b')) {
moveUp(count() * (linesOnScreen() - 2) + cursorLineOnScreen());
scrollToLine(cursorLine() + linesOnScreen() - 2);
handleStartOfLine();
finishMovement();
} else if (input.isKey(Key_Delete)) {
setAnchor();
moveRight(qMin(1, rightDist()));
2010-05-12 11:18:18 +02:00
removeText(currentRange());
if (atEndOfLine())
moveLeft();
} else if (input.isKey(Key_BracketLeft) || input.isKey(Key_BracketRight)) {
} else if (input.isControl(Key_BracketRight)) {
handleExCommand("tag");
} else {
//qDebug() << "IGNORED IN COMMAND MODE: " << key << text
// << " VISUAL: " << m_visualMode;
// if a key which produces text was pressed, don't mark it as unhandled
// - otherwise the text would be inserted while being in command mode
if (input.text().isEmpty()) {
handled = EventUnhandled;
}
}
m_positionPastEnd = (m_visualTargetColumn == -1) && isVisualMode();
return handled;
}
EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
{
if (input.isEscape()) {
moveLeft(qMin(1, leftDist()));
setTargetColumn();
m_submode = NoSubMode;
m_mode = CommandMode;
finishMovement();
updateMiniBuffer();
} else if (input.isKey(Key_Left)) {
breakEditBlock();
moveLeft(1);
setTargetColumn();
} else if (input.isKey(Key_Right)) {
breakEditBlock();
moveRight(1);
setTargetColumn();
} else if (input.isKey(Key_Up)) {
breakEditBlock();
moveUp(1);
setTargetColumn();
} else if (input.isKey(Key_Down)) {
breakEditBlock();
moveDown(1);
} else {
joinPreviousEditBlock();
if (!atEndOfLine()) {
setAnchor();
moveRight();
2010-05-12 11:18:18 +02:00
m_lastDeletion += selectText(Range(position(), anchor()));
removeText(currentRange());
}
const QString text = input.text();
m_lastInsertion += text;
2010-09-16 16:56:11 +02:00
setAnchor();
2010-05-11 14:26:37 +02:00
insertText(text);
endEditBlock();
setTargetColumn();
}
return EventHandled;
}
2010-03-26 13:22:06 +01:00
EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
{
//const int key = input.key;
//const QString &text = input.text;
2010-03-26 13:22:06 +01:00
if (input.isEscape()) {
if (isVisualBlockMode() && !m_lastInsertion.contains('\n')) {
leaveVisualMode();
joinPreviousEditBlock();
moveLeft(m_lastInsertion.size());
setAnchor();
int pos = position();
setTargetColumn();
for (int i = 0; i < m_visualInsertCount; ++i) {
moveDown();
2010-05-11 14:26:37 +02:00
insertText(m_lastInsertion);
}
moveLeft(1);
Range range(pos, position(), RangeBlockMode);
yankText(range);
setPosition(pos);
setDotCommand("p");
endEditBlock();
} else {
2010-05-11 14:26:37 +02:00
// Normal insertion. Start with '1', as one instance was
// already physically inserted while typing.
QString data;
for (int i = 1; i < count(); ++i)
data += m_lastInsertion;
2010-05-11 14:26:37 +02:00
insertText(data);
moveLeft(qMin(1, leftDist()));
setTargetColumn();
leaveVisualMode();
breakEditBlock();
}
2010-05-05 15:50:05 +02:00
g.dotCommand += m_lastInsertion;
g.dotCommand += QChar(27);
enterCommandMode();
m_submode = NoSubMode;
m_ctrlVActive = false;
m_opcount.clear();
m_mvcount.clear();
} else if (m_ctrlVActive) {
insertInInsertMode(input.raw());
} else if (input.isControl('v')) {
m_ctrlVActive = true;
} else if (input.isControl('w')) {
int endPos = position();
moveToNextWordStart(count(), false, false);
setTargetColumn();
int beginPos = position();
Range range(beginPos, endPos, RangeCharMode);
removeText(range);
} else if (input.isKey(Key_Insert)) {
if (m_mode == ReplaceMode)
m_mode = InsertMode;
else
m_mode = ReplaceMode;
} else if (input.isKey(Key_Left)) {
2009-01-16 16:15:01 +01:00
moveLeft(count());
setTargetColumn();
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isControl(Key_Left)) {
moveToNextWordStart(count(), false, false);
setTargetColumn();
breakEditBlock();
m_lastInsertion.clear();
} else if (input.isKey(Key_Down)) {
//removeAutomaticIndentation();
m_submode = NoSubMode;
2009-01-16 16:15:01 +01:00
moveDown(count());
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isKey(Key_Up)) {
//removeAutomaticIndentation();
m_submode = NoSubMode;
2009-01-16 16:15:01 +01:00
moveUp(count());
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isKey(Key_Right)) {
2009-01-16 16:15:01 +01:00
moveRight(count());
setTargetColumn();
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isControl(Key_Right)) {
moveToNextWordStart(count(), false, true);
moveRight(); // we need one more move since we are in insert mode
setTargetColumn();
breakEditBlock();
m_lastInsertion.clear();
} else if (input.isKey(Key_Home)) {
moveToStartOfLine();
setTargetColumn();
breakEditBlock();
m_lastInsertion.clear();
} else if (input.isKey(Key_End)) {
if (count() > 1)
moveDown(count() - 1);
moveBehindEndOfLine();
setTargetColumn();
breakEditBlock();
m_lastInsertion.clear();
} else if (input.isReturn()) {
joinPreviousEditBlock();
m_submode = NoSubMode;
insertText(QString("\n"));
2011-02-23 15:28:39 +01:00
m_lastInsertion += '\n';
2009-04-03 11:54:29 +02:00
insertAutomaticIndentation(true);
setTargetColumn();
endEditBlock();
} else if (input.isBackspace()) {
joinPreviousEditBlock();
m_justAutoIndented = 0;
if (!m_lastInsertion.isEmpty()
|| hasConfig(ConfigBackspace, "start")
|| hasConfig(ConfigBackspace, "2")) {
const int line = cursorLine() + 1;
const Column col = cursorColumn();
QString data = lineContents(line);
const Column ind = indentation(data);
if (col.logical <= ind.logical && col.logical
&& startsWithWhitespace(data, col.physical)) {
const int ts = config(ConfigTabStop).toInt();
const int newl = col.logical - 1 - (col.logical - 1) % ts;
const QString prefix = tabExpand(newl);
setLineContents(line, prefix + data.mid(col.physical));
moveToStartOfLine();
moveRight(prefix.size());
m_lastInsertion.clear(); // FIXME
} else {
setAnchor();
2010-09-14 16:58:31 +02:00
cursor().deletePreviousChar();
m_lastInsertion.chop(1);
2009-04-03 11:54:29 +02:00
}
setTargetColumn();
}
endEditBlock();
} else if (input.isKey(Key_Delete)) {
setAnchor();
2010-09-14 16:58:31 +02:00
cursor().deleteChar();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isKey(Key_PageDown) || input.isControl('f')) {
2009-04-03 11:54:29 +02:00
removeAutomaticIndentation();
2009-01-16 16:15:01 +01:00
moveDown(count() * (linesOnScreen() - 2));
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isKey(Key_PageUp) || input.isControl('b')) {
2009-04-03 11:54:29 +02:00
removeAutomaticIndentation();
2009-01-16 16:15:01 +01:00
moveUp(count() * (linesOnScreen() - 2));
breakEditBlock();
2008-12-27 12:24:50 +01:00
m_lastInsertion.clear();
} else if (input.isKey(Key_Tab)) {
m_justAutoIndented = 0;
if (hasConfig(ConfigExpandTab)) {
const int ts = config(ConfigTabStop).toInt();
const int col = logicalCursorColumn();
QString str = QString(ts - col % ts, ' ');
m_lastInsertion.append(str);
insertText(str);
setTargetColumn();
} else {
insertInInsertMode(input.raw());
}
} else if (input.isControl('d')) {
// remove one level of indentation from the current line
int shift = config(ConfigShiftWidth).toInt();
int tab = config(ConfigTabStop).toInt();
int line = cursorLine() + 1;
int pos = firstPositionInLine(line);
QString text = lineContents(line);
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;
}
removeText(Range(pos, pos+i));
//} else if (key >= control('a') && key <= control('z')) {
// // ignore these
} else if (input.isControl('p') || input.isControl('n')) {
QTextCursor tc = EDITOR(textCursor());
moveToNextWordStart(count(), false, false);
QString str = selectText(Range(position(), tc.position()));
EDITOR(setTextCursor(tc));
emit q->simpleCompletionRequested(str, input.isControl('n'));
} else if (!input.text().isEmpty()) {
insertInInsertMode(input.text());
} else {
// We don't want fancy stuff in insert mode.
return EventHandled;
}
updateMiniBuffer();
return EventHandled;
}
void FakeVimHandler::Private::insertInInsertMode(const QString &text)
{
joinPreviousEditBlock();
m_justAutoIndented = 0;
m_lastInsertion.append(text);
insertText(text);
if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
2010-09-13 15:23:20 +02:00
const QString leftText = block().text()
.left(position() - 1 - block().position());
if (leftText.simplified().isEmpty()) {
Range range(position(), position(), m_rangemode);
indentText(range, text.at(0));
}
}
setTargetColumn();
endEditBlock();
m_ctrlVActive = false;
}
2010-05-05 16:23:39 +02:00
EventResult FakeVimHandler::Private::handleExMode(const Input &input)
{
if (input.isEscape()) {
g.commandBuffer.clear();
enterCommandMode();
m_ctrlVActive = false;
} else if (m_ctrlVActive) {
g.commandBuffer.insertChar(input.raw());
m_ctrlVActive = false;
} else if (input.isControl('v')) {
m_ctrlVActive = true;
return EventHandled;
} else if (input.isBackspace()) {
if (g.commandBuffer.isEmpty())
enterCommandMode();
else
g.commandBuffer.deleteChar();
2010-09-20 18:04:35 +02:00
} else if (input.isKey(Key_Tab)) {
// FIXME: Complete actual commands.
g.commandBuffer.historyUp();
} else if (input.isKey(Key_Left)) {
g.commandBuffer.moveLeft();
} else if (input.isReturn()) {
showMessage(MessageCommand, g.commandBuffer.display());
handleExCommand(g.commandBuffer.contents());
g.commandBuffer.clear();
if (m_textedit || m_plaintextedit)
leaveVisualMode();
2010-05-05 16:23:39 +02:00
} else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
g.commandBuffer.historyUp();
2010-05-05 16:23:39 +02:00
} else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
g.commandBuffer.historyDown();
} else if (!g.commandBuffer.handleInput(input)) {
2010-05-05 16:23:39 +02:00
qDebug() << "IGNORED IN EX-MODE: " << input.key() << input.text();
return EventUnhandled;
}
updateMiniBuffer();
2010-05-05 16:23:39 +02:00
return EventHandled;
}
EventResult FakeVimHandler::Private::handleSearchSubSubMode(const Input &input)
{
if (input.isEscape()) {
g.currentMessage.clear();
g.searchBuffer.clear();
setAnchorAndPosition(m_searchStartPosition, m_searchStartPosition);
scrollToLine(m_searchFromScreenLine);
2010-05-05 16:23:39 +02:00
enterCommandMode();
} else if (input.isBackspace()) {
if (g.searchBuffer.isEmpty()) {
2010-05-05 16:23:39 +02:00
enterCommandMode();
} else {
g.searchBuffer.deleteChar();
}
2010-05-05 16:23:39 +02:00
} else if (input.isKey(Key_Left)) {
g.searchBuffer.moveLeft();
} else if (input.isKey(Key_Right)) {
g.searchBuffer.moveRight();
} else if (input.isReturn()) {
const QString &needle = g.searchBuffer.contents();
if (!needle.isEmpty())
m_lastSearch = needle;
else
g.searchBuffer.setContents(m_lastSearch);
if (!m_lastSearch.isEmpty()) {
updateFind(true);
finishMovement(g.searchBuffer.prompt() + m_lastSearch + '\n');
} else {
finishMovement();
2008-12-28 00:32:07 +01:00
}
if (g.currentMessage.isEmpty())
showMessage(MessageCommand, g.searchBuffer.display());
enterCommandMode();
g.searchBuffer.clear();
2010-05-05 16:23:39 +02:00
} else if (input.isKey(Key_Up) || input.isKey(Key_PageUp)) {
g.searchBuffer.historyUp();
2010-05-05 16:23:39 +02:00
} else if (input.isKey(Key_Down) || input.isKey(Key_PageDown)) {
g.searchBuffer.historyDown();
} else if (input.isKey(Key_Tab)) {
g.searchBuffer.insertChar(QChar(9));
} else if (!g.searchBuffer.handleInput(input)) {
//qDebug() << "IGNORED IN SEARCH MODE: " << input.key() << input.text();
return EventUnhandled;
}
updateMiniBuffer();
if (!input.isReturn() && !input.isEscape())
updateFind(false);
return EventHandled;
}
2010-05-11 14:26:37 +02:00
// This uses 1 based line counting.
2008-12-28 02:15:26 +01:00
int FakeVimHandler::Private::readLineCode(QString &cmd)
{
//qDebug() << "CMD: " << cmd;
if (cmd.isEmpty())
return -1;
QChar c = cmd.at(0);
cmd = cmd.mid(1);
2010-05-11 14:26:37 +02:00
if (c == '.') {
if (cmd.isEmpty())
return cursorLine() + 1;
2010-05-11 14:26:37 +02:00
QChar c1 = cmd.at(0);
if (c1 == '+' || c1 == '-') {
// Repeat for things like .+4
cmd = cmd.mid(1);
return cursorLine() + readLineCode(cmd);
2010-05-11 14:26:37 +02:00
}
return cursorLine() + 1;
2010-05-11 14:26:37 +02:00
}
2008-12-28 02:15:26 +01:00
if (c == '$')
return linesInDocument();
2008-12-29 14:47:42 +01:00
if (c == '\'' && !cmd.isEmpty()) {
2010-05-11 14:26:37 +02:00
if (cmd.isEmpty()) {
showMessage(MessageError, msgMarkNotSet(QString()));
2010-05-11 14:26:37 +02:00
return -1;
}
int m = mark(cmd.at(0).unicode());
if (m == -1) {
showMessage(MessageError, msgMarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
2008-12-29 14:47:42 +01:00
return -1;
}
cmd = cmd.mid(1);
2010-05-11 14:26:37 +02:00
return lineForPosition(m);
2008-12-29 14:47:42 +01:00
}
2008-12-28 02:15:26 +01:00
if (c == '-') {
int n = readLineCode(cmd);
return cursorLine() + 1 - (n == -1 ? 1 : n);
2008-12-28 02:15:26 +01:00
}
if (c == '+') {
int n = readLineCode(cmd);
return cursorLine() + 1 + (n == -1 ? 1 : n);
2008-12-28 02:15:26 +01:00
}
if (c == '\'' && !cmd.isEmpty()) {
2010-05-11 14:26:37 +02:00
int pos = mark(cmd.at(0).unicode());
2009-01-08 17:21:51 +01:00
if (pos == -1) {
showMessage(MessageError, msgMarkNotSet(cmd.at(0)));
cmd = cmd.mid(1);
return -1;
}
cmd = cmd.mid(1);
return lineForPosition(pos);
}
2008-12-28 02:15:26 +01:00
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;
2008-12-28 02:15:26 +01:00
return n;
}
2010-05-11 14:26:37 +02:00
// Parsing failed.
2008-12-28 02:15:26 +01:00
cmd = c + cmd;
return -1;
2008-12-28 02:15:26 +01:00
}
2010-05-12 11:18:18 +02:00
void FakeVimHandler::Private::setCurrentRange(const Range &range)
2009-01-06 11:33:07 +01:00
{
2010-09-14 16:58:31 +02:00
setAnchorAndPosition(range.beginPos, range.endPos);
2010-05-12 11:18:18 +02:00
m_rangemode = range.rangemode;
2009-01-06 11:33:07 +01:00
}
Range FakeVimHandler::Private::rangeFromCurrentLine() const
{
Range range;
int line = cursorLine() + 1;
range.beginPos = firstPositionInLine(line);
range.endPos = lastPositionInLine(line);
return range;
}
// use handleExCommand for invoking commands that might move the cursor
2009-04-06 10:58:48 +02:00
void FakeVimHandler::Private::handleCommand(const QString &cmd)
{
handleExCommand(cmd);
}
2010-05-11 14:26:37 +02:00
bool FakeVimHandler::Private::handleExSubstituteCommand(const ExCommand &cmd)
// :substitute
{
QString flags;
QRegExp pattern;
QString replacement;
int count = 0;
if (cmd.cmd.startsWith("&&")) {
flags = cmd.cmd.mid(2);
if (flags.isEmpty())
flags = m_lastSubstituteFlags;
pattern = m_lastSubstitutePattern;
replacement = m_lastSubstituteReplacement;
count = cmd.args.section(QLatin1Char(' '), 1, 1).toInt();
} else if (cmd.cmd.startsWith(QLatin1Char('&'))) {
flags = cmd.cmd.mid(1);
if (flags.isEmpty())
flags = m_lastSubstituteFlags;
pattern = m_lastSubstitutePattern;
replacement = m_lastSubstituteReplacement;
count = cmd.args.section(QLatin1Char(' '), 1, 1).toInt();
} else if (cmd.matches("s", "substitute")) {
flags = m_lastSubstituteFlags;
if (flags.isEmpty())
flags = m_lastSubstituteFlags;
pattern = m_lastSubstitutePattern;
replacement = m_lastSubstituteReplacement;
count = cmd.args.section(QLatin1Char(' '), 2, 2).toInt();
} else {
QString line = cmd.cmd + ' ' + cmd.args;
line = line.trimmed();
if (line.startsWith(_("substitute")))
line = line.mid(10);
else if (line.startsWith('s') && line.size() > 1
&& !isalpha(line.at(1).unicode()))
line = line.mid(1);
else
return false;
// we have /{pattern}/{string}/[flags] now
if (line.isEmpty())
return false;
const QChar separator = line.at(0);
int pos1 = -1;
int pos2 = -1;
int i;
for (i = 1; i < line.size(); ++i) {
if (line.at(i) == separator && line.at(i - 1) != '\\') {
pos1 = i;
break;
}
}
if (pos1 == -1)
return false;
for (++i; i < line.size(); ++i) {
if (line.at(i) == separator && line.at(i - 1) != '\\') {
pos2 = i;
break;
}
}
if (pos2 == -1)
pos2 = line.size();
QString needle = line.mid(1, pos1 - 1);
replacement = line.mid(pos1 + 1, pos2 - pos1 - 1);
flags = line.mid(pos2 + 1);
needle.replace('$', '\n');
needle.replace("\\\n", "\\$");
pattern = vimPatternToQtPattern(needle, hasConfig(ConfigSmartCase));
m_lastSubstituteFlags = flags;
m_lastSubstitutePattern = pattern;
m_lastSubstituteReplacement = replacement;
}
if (count == 0)
count = 1;
if (flags.contains('i'))
pattern.setCaseSensitivity(Qt::CaseInsensitive);
beginEditBlock();
const bool global = flags.contains('g');
for (int a = 0; a != count; ++a) {
const Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
const int beginLine = lineForPosition(range.beginPos);
const int endLine = lineForPosition(range.endPos);
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();
}
}
repl.replace("\\&", "&");
text = text.left(pos) + repl + text.mid(pos + matched.size());
pos += repl.size();
if (!global)
break;
}
if (text != origText)
setLineContents(line, text);
}
}
moveToStartOfLine();
setTargetColumn();
endEditBlock();
return true;
}
2010-05-11 14:26:37 +02:00
bool FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map
2010-03-19 14:31:21 +01:00
{
QByteArray modes;
enum Type { Map, Noremap, Unmap } type;
QByteArray cmd = cmd0.cmd.toLatin1();
2010-03-19 14:31:21 +01:00
// Strange formatting. But everything else is even uglier.
if (cmd == "map") { modes = "nvo"; type = Map; } else
if (cmd == "nm" || cmd == "nmap") { modes = "n"; type = Map; } else
if (cmd == "vm" || cmd == "vmap") { modes = "v"; type = Map; } else
if (cmd == "xm" || cmd == "xmap") { modes = "x"; type = Map; } else
if (cmd == "smap") { modes = "s"; type = Map; } else
if (cmd == "map!") { modes = "ic"; type = Map; } else
if (cmd == "im" || cmd == "imap") { modes = "i"; type = Map; } else
if (cmd == "lm" || cmd == "lmap") { modes = "l"; type = Map; } else
if (cmd == "cm" || cmd == "cmap") { modes = "c"; type = Map; } else
if (cmd == "no" || cmd == "noremap") { modes = "nvo"; type = Noremap; } else
if (cmd == "nn" || cmd == "nnoremap") { modes = "n"; type = Noremap; } else
if (cmd == "vn" || cmd == "vnoremap") { modes = "v"; type = Noremap; } else
if (cmd == "xn" || cmd == "xnoremap") { modes = "x"; type = Noremap; } else
if (cmd == "snor" || cmd == "snoremap") { modes = "s"; type = Noremap; } else
if (cmd == "ono" || cmd == "onoremap") { modes = "o"; type = Noremap; } else
if (cmd == "no!" || cmd == "noremap!") { modes = "ic"; type = Noremap; } else
if (cmd == "ino" || cmd == "inoremap") { modes = "i"; type = Noremap; } else
if (cmd == "ln" || cmd == "lnoremap") { modes = "l"; type = Noremap; } else
if (cmd == "cno" || cmd == "cnoremap") { modes = "c"; type = Noremap; } else
if (cmd == "unm" || cmd == "unmap") { modes = "nvo"; type = Unmap; } else
if (cmd == "nun" || cmd == "nunmap") { modes = "n"; type = Unmap; } else
if (cmd == "vu" || cmd == "vunmap") { modes = "v"; type = Unmap; } else
if (cmd == "xu" || cmd == "xunmap") { modes = "x"; type = Unmap; } else
if (cmd == "sunm" || cmd == "sunmap") { modes = "s"; type = Unmap; } else
if (cmd == "ou" || cmd == "ounmap") { modes = "o"; type = Unmap; } else
if (cmd == "unm!" || cmd == "unmap!") { modes = "ic"; type = Unmap; } else
if (cmd == "iu" || cmd == "iunmap") { modes = "i"; type = Unmap; } else
if (cmd == "lu" || cmd == "lunmap") { modes = "l"; type = Unmap; } else
if (cmd == "cu" || cmd == "cunmap") { modes = "c"; type = Unmap; }
else
return false;
QString args = cmd0.args;
bool silent = false;
bool unique = false;
forever {
if (eatString("<silent>", &args)) {
silent = true;
} else if (eatString("<unique>", &args)) {
continue;
} else if (eatString("<special>", &args)) {
continue;
} else if (eatString("<buffer>", &args)) {
notImplementedYet();
continue;
} else if (eatString("<script>", &args)) {
notImplementedYet();
continue;
} else if (eatString("<expr>", &args)) {
notImplementedYet();
return true;
}
break;
}
const QString lhs = args.section(QRegExp("\\s+"), 0, 0);
const QString rhs = args.section(QRegExp("\\s+"), 1);
if ((rhs.isNull() && type != Unmap) || (!rhs.isNull() && type == Unmap)) {
2010-05-10 09:01:30 +02:00
// FIXME: Dump mappings here.
//qDebug() << g.mappings;
return true;
2010-05-10 09:01:30 +02:00
}
Inputs key(lhs);
//qDebug() << "MAPPING: " << modes << lhs << rhs;
2010-03-19 14:31:21 +01:00
switch (type) {
case Unmap:
foreach (char c, modes)
MappingsIterator(&g.mappings, c, key).remove();
2010-03-19 14:31:21 +01:00
break;
case Map: // fall through
2010-03-26 13:22:06 +01:00
case Noremap: {
Inputs inputs(rhs, type == Noremap, silent);
// TODO: Use MappingsIterator to insert mapping!
2010-03-19 14:31:21 +01:00
foreach (char c, modes)
MappingsIterator(&g.mappings, c).setInputs(key, inputs, unique);
2010-03-19 14:31:21 +01:00
break;
2010-03-26 13:22:06 +01:00
}
2010-03-19 14:31:21 +01:00
}
return true;
}
bool FakeVimHandler::Private::handleExHistoryCommand(const ExCommand &cmd)
{
// :his[tory]
if (!cmd.matches("his", "history"))
return false;
2009-04-01 15:52:48 +02:00
if (cmd.args.isEmpty()) {
QString info;
info += "# command history\n";
int i = 0;
foreach (const QString &item, g.commandBuffer.historyItems()) {
++i;
info += QString("%1 %2\n").arg(i, -8).arg(item);
}
emit q->extraInformationChanged(info);
} else {
notImplementedYet();
}
updateMiniBuffer();
return true;
}
2008-12-28 02:15:26 +01:00
2010-05-20 14:08:11 +02:00
bool FakeVimHandler::Private::handleExRegisterCommand(const ExCommand &cmd)
{
// :reg[isters] and :di[splay]
if (!cmd.matches("reg", "registers") && !cmd.matches("di", "display"))
2010-05-20 14:08:11 +02:00
return false;
QByteArray regs = cmd.args.toLatin1();
if (regs.isEmpty()) {
regs = "\"0123456789";
QHashIterator<int, Register> it(g.registers);
while (it.hasNext()) {
it.next();
if (it.key() > '9')
regs += char(it.key());
}
}
QString info;
info += "--- Registers ---\n";
foreach (char reg, regs) {
QString value = quoteUnprintable(registerContents(reg));
2010-05-20 14:08:11 +02:00
info += QString("\"%1 %2\n").arg(reg).arg(value);
}
emit q->extraInformationChanged(info);
updateMiniBuffer();
return true;
}
bool FakeVimHandler::Private::handleExSetCommand(const ExCommand &cmd)
{
// :se[t]
if (!cmd.matches("se", "set"))
return false;
clearMessage();
SavedAction *act = theFakeVimSettings()->item(cmd.args);
QTC_CHECK(!cmd.args.isEmpty()); // Handled by plugin.
if (act && act->value().canConvert(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.
showMessage(MessageInfo, cmd.args + '=' + act->value().toString());
} else if (cmd.args.startsWith(_("no"))
&& (act = theFakeVimSettings()->item(cmd.args.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 (cmd.args.contains('=')) {
// Non-boolean config to set.
int p = cmd.args.indexOf('=');
QString error = theFakeVimSettings()
->trySetValue(cmd.args.left(p), cmd.args.mid(p + 1));
if (!error.isEmpty())
showMessage(MessageError, error);
} else {
showMessage(MessageError, FakeVimHandler::tr("Unknown option: ") + cmd.args);
2008-12-28 02:15:26 +01:00
}
updateMiniBuffer();
updateEditor();
return true;
}
bool FakeVimHandler::Private::handleExNormalCommand(const ExCommand &cmd)
{
// :norm[al]
if (!cmd.matches("norm", "normal"))
return false;
//qDebug() << "REPLAY NORMAL: " << quoteUnprintable(reNormal.cap(3));
replay(cmd.args, 1);
return true;
}
2008-12-28 02:15:26 +01:00
bool FakeVimHandler::Private::handleExDeleteCommand(const ExCommand &cmd)
{
// :d[elete]
if (!cmd.matches("d", "delete"))
return false;
2009-10-16 11:30:46 +02:00
Range range = cmd.range.endPos == 0 ? rangeFromCurrentLine() : cmd.range;
setCurrentRange(range);
QString reg = cmd.args;
QString text = selectText(range);
2010-05-12 11:18:18 +02:00
removeText(currentRange());
if (!reg.isEmpty()) {
const int r = reg.at(0).unicode();
setRegister(r, text, RangeLineMode);
}
return true;
}
2008-12-28 02:15:26 +01:00
2010-05-11 14:26:37 +02:00
bool FakeVimHandler::Private::handleExWriteCommand(const ExCommand &cmd)
{
// :w, :x, :wq, ...
//static QRegExp reWrite("^[wx]q?a?!?( (.*))?$");
if (cmd.cmd != "w" && cmd.cmd != "x" && cmd.cmd != "wq")
return false;
2010-05-12 11:18:18 +02:00
int beginLine = lineForPosition(cmd.range.beginPos);
int endLine = lineForPosition(cmd.range.endPos);
const bool noArgs = (beginLine == -1);
if (beginLine == -1)
beginLine = 0;
if (endLine == -1)
endLine = linesInDocument();
//qDebug() << "LINES: " << beginLine << endLine;
//QString prefix = cmd.args;
const bool forced = cmd.hasBang;
//const bool quit = prefix.contains(QChar('q')) || prefix.contains(QChar('x'));
//const bool quitAll = quit && prefix.contains(QChar('a'));
QString fileName = cmd.args;
if (fileName.isEmpty())
fileName = m_currentFileName;
QFile file1(fileName);
const bool exists = file1.exists();
if (exists && !forced && !noArgs) {
showMessage(MessageError, FakeVimHandler::tr
("File \"%1\" exists (add ! to override)").arg(fileName));
} else if (file1.open(QIODevice::ReadWrite)) {
// Nobody cared, so act ourselves.
file1.close();
Range range(firstPositionInLine(beginLine),
firstPositionInLine(endLine), RangeLineMode);
2010-05-12 11:18:18 +02:00
QString contents = selectText(range);
QFile::remove(fileName);
QFile file2(fileName);
if (file2.open(QIODevice::ReadWrite)) {
QTextStream ts(&file2);
ts << contents;
} else {
showMessage(MessageError, 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();
showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2 %3L, %4C written")
2010-08-19 14:04:15 +02:00
.arg(fileName).arg(exists ? " " : tr(" [New] "))
.arg(ba.count('\n')).arg(ba.size()));
//if (quitAll)
// passUnknownExCommand(forced ? "qa!" : "qa");
//else if (quit)
// passUnknownExCommand(forced ? "q!" : "q");
2008-12-28 02:15:26 +01:00
} else {
showMessage(MessageError, FakeVimHandler::tr
("Cannot open file \"%1\" for reading").arg(fileName));
}
return true;
}
bool FakeVimHandler::Private::handleExReadCommand(const ExCommand &cmd)
{
// :r[ead]
if (!cmd.matches("r", "read"))
return false;
beginEditBlock();
moveToStartOfLine();
setTargetColumn();
moveDown();
m_currentFileName = cmd.args;
QFile file(m_currentFileName);
file.open(QIODevice::ReadOnly);
QTextStream ts(&file);
QString data = ts.readAll();
2010-09-14 16:58:31 +02:00
insertText(data);
endEditBlock();
showMessage(MessageInfo, FakeVimHandler::tr("\"%1\" %2L, %3C")
.arg(m_currentFileName).arg(data.count('\n')).arg(data.size()));
return true;
}
2010-05-11 14:26:37 +02:00
bool FakeVimHandler::Private::handleExBangCommand(const ExCommand &cmd) // :!
{
if (!cmd.cmd.startsWith(QLatin1Char('!')))
return false;
2010-05-12 11:18:18 +02:00
setCurrentRange(cmd.range);
int targetPosition = firstPositionInLine(lineForPosition(cmd.range.beginPos));
QString command = QString(cmd.cmd.mid(1) + ' ' + cmd.args).trimmed();
2010-05-12 11:18:18 +02:00
QString text = selectText(cmd.range);
QProcess proc;
2010-05-04 22:24:48 +02:00
proc.start(command);
proc.waitForStarted();
if (Utils::HostOsInfo::isWindowsHost())
text.replace(_("\n"), _("\r\n"));
proc.write(text.toUtf8());
proc.closeWriteChannel();
proc.waitForFinished();
QString result = QString::fromUtf8(proc.readAllStandardOutput());
if (text.isEmpty()) {
emit q->extraInformationChanged(result);
} else {
beginEditBlock();
removeText(currentRange());
insertText(result);
setPosition(targetPosition);
endEditBlock();
leaveVisualMode();
//qDebug() << "FILTER: " << command;
showMessage(MessageInfo, FakeVimHandler::tr("%n lines filtered", 0,
text.count('\n')));
}
return true;
}
bool FakeVimHandler::Private::handleExShiftCommand(const ExCommand &cmd)
{
if (cmd.cmd != "<" && cmd.cmd != ">")
return false;
Range range = cmd.range;
if (cmd.range.endPos == 0)
range = rangeFromCurrentLine();
setCurrentRange(range);
int count = qMax(1, cmd.args.toInt());
if (cmd.cmd == "<")
shiftRegionLeft(count);
else
shiftRegionRight(count);
leaveVisualMode();
const int beginLine = lineForPosition(range.beginPos);
const int endLine = lineForPosition(range.endPos);
showMessage(MessageInfo, FakeVimHandler::tr("%n lines %1ed %2 time", 0,
(endLine - beginLine + 1)).arg(cmd.cmd).arg(count));
return true;
}
2010-07-14 16:04:10 +02:00
bool FakeVimHandler::Private::handleExNohlsearchCommand(const ExCommand &cmd)
{
// :nohlsearch
if (!cmd.cmd.startsWith("noh"))
return false;
highlightMatches(QString());
2010-07-14 16:04:10 +02:00
return true;
}
bool FakeVimHandler::Private::handleExRedoCommand(const ExCommand &cmd)
{
// :redo
if (cmd.cmd != "red" && cmd.cmd != "redo")
return false;
redo();
updateMiniBuffer();
return true;
}
bool FakeVimHandler::Private::handleExUndoCommand(const ExCommand &cmd)
{
// :undo
if (cmd.cmd != "u" && cmd.cmd != "un" && cmd.cmd != "undo")
return false;
undo();
updateMiniBuffer();
return true;
}
bool FakeVimHandler::Private::handleExGotoCommand(const ExCommand &cmd)
{
// :<nr>
if (!cmd.cmd.isEmpty())
return false;
2010-05-12 11:18:18 +02:00
const int beginLine = lineForPosition(cmd.range.beginPos);
setPosition(firstPositionInLine(beginLine));
clearMessage();
return true;
}
bool FakeVimHandler::Private::handleExSourceCommand(const ExCommand &cmd)
{
// :source
if (cmd.cmd != "so" && cmd.cmd != "source")
return false;
QString fileName = cmd.args;
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
showMessage(MessageError, FakeVimHandler::tr("Cannot open file %1").arg(fileName));
return true;
}
bool inFunction = false;
QByteArray line;
while (!file.atEnd() || !line.isEmpty()) {
QByteArray nextline = !file.atEnd() ? file.readLine() : QByteArray();
// remove comment
int i = nextline.lastIndexOf('"');
if (i != -1)
nextline = nextline.remove(i, nextline.size() - i);
nextline = nextline.trimmed();
// multi-line command?
if (nextline.startsWith('\\')) {
line += nextline.mid(1);
continue;
}
if (line.startsWith("function")) {
//qDebug() << "IGNORING FUNCTION" << line;
inFunction = true;
} else if (inFunction && line.startsWith("endfunction")) {
inFunction = false;
} else if (!line.isEmpty() && !inFunction) {
//qDebug() << "EXECUTING: " << line;
ExCommand cmd;
cmd.setContentsFromLine(QString::fromLocal8Bit(line));
while (cmd.nextSubcommand()) {
if (!handleExCommandHelper(cmd))
break;
}
}
line = nextline;
}
file.close();
return true;
}
bool FakeVimHandler::Private::handleExEchoCommand(const ExCommand &cmd)
{
// :echo
if (cmd.cmd != "echo")
return false;
showMessage(MessageInfo, cmd.args);
return true;
}
void FakeVimHandler::Private::handleExCommand(const QString &line0)
{
QString line = line0; // Make sure we have a copy to prevent aliasing.
if (line.endsWith(QLatin1Char('%'))) {
line.chop(1);
int percent = line.toInt();
setPosition(firstPositionInLine(percent * linesInDocument() / 100));
clearMessage();
return;
}
ExCommand cmd;
cmd.setContentsFromLine(line);
//qDebug() << "CMD: " << cmd;
beginEditBlock();
while (cmd.nextSubcommand()) {
if (!handleExCommandHelper(cmd)) {
showMessage(MessageError,
tr("Not an editor command: %1").arg(cmd.printCommand()));
break;
}
}
endEditBlock();
enterCommandMode();
}
bool FakeVimHandler::Private::handleExCommandHelper(ExCommand &cmd)
{
// parse range first
QString &line = cmd.cmd;
2010-05-11 14:26:37 +02:00
// FIXME: that seems to be different for %w and %s
if (line.startsWith(QLatin1Char('%')))
line.replace(0, 1, "1,$");
2010-05-11 14:26:37 +02:00
const int beginLine = readLineCode(line);
2010-05-12 11:18:18 +02:00
int endLine = -1;
2010-05-11 14:26:37 +02:00
if (line.startsWith(',')) {
line = line.mid(1);
2010-05-12 11:18:18 +02:00
endLine = readLineCode(line);
2010-05-11 14:26:37 +02:00
}
if (beginLine != -1 && endLine == -1)
endLine = beginLine;
if (beginLine != -1) {
const int beginPos = firstPositionInLine(beginLine);
const int endPos = lastPositionInLine(endLine);
cmd.range = Range(beginPos, endPos, RangeLineMode);
cmd.count = beginLine;
}
2010-05-11 14:26:37 +02:00
return handleExPluginCommand(cmd)
|| handleExGotoCommand(cmd)
2010-05-11 14:26:37 +02:00
|| handleExBangCommand(cmd)
|| handleExHistoryCommand(cmd)
2010-05-20 14:08:11 +02:00
|| handleExRegisterCommand(cmd)
2010-05-11 14:26:37 +02:00
|| handleExDeleteCommand(cmd)
|| handleExMapCommand(cmd)
2010-07-14 16:04:10 +02:00
|| handleExNohlsearchCommand(cmd)
2010-05-11 14:26:37 +02:00
|| handleExNormalCommand(cmd)
|| handleExReadCommand(cmd)
|| handleExRedoCommand(cmd)
|| handleExUndoCommand(cmd)
2010-05-11 14:26:37 +02:00
|| handleExSetCommand(cmd)
|| handleExShiftCommand(cmd)
2010-05-11 14:26:37 +02:00
|| handleExSourceCommand(cmd)
|| handleExSubstituteCommand(cmd)
|| handleExWriteCommand(cmd)
|| handleExEchoCommand(cmd);
}
bool FakeVimHandler::Private::handleExPluginCommand(const ExCommand &cmd)
{
bool handled = false;
emit q->handleExCommandRequested(&handled, cmd);
//qDebug() << "HANDLER REQUEST: " << cmd.cmd << handled;
return handled;
}
2010-07-14 13:02:17 +02:00
void FakeVimHandler::Private::searchBalanced(bool forward, QChar needle, QChar other)
{
int level = 1;
2010-09-13 15:23:20 +02:00
int pos = position();
2010-07-14 13:02:17 +02:00
const int npos = forward ? lastPositionInDocument() : 0;
while (true) {
if (forward)
++pos;
else
--pos;
if (pos == npos)
return;
QChar c = document()->characterAt(pos);
2010-07-14 13:02:17 +02:00
if (c == other)
++level;
else if (c == needle)
--level;
if (level == 0) {
const int oldLine = cursorLine() - cursorLineOnScreen();
// Making this unconditional feels better, but is not "vim like".
if (oldLine != cursorLine() - cursorLineOnScreen())
scrollToLine(cursorLine() - linesOnScreen() / 2);
recordJump();
setPosition(pos);
2010-07-14 13:02:17 +02:00
setTargetColumn();
return;
}
}
}
void FakeVimHandler::Private::search(const SearchData &sd, bool showMessages)
{
2008-12-26 17:01:21 +01:00
QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively;
if (!sd.forward)
2009-01-16 09:56:08 +01:00
flags |= QTextDocument::FindBackward;
QRegExp needleExp = vimPatternToQtPattern(sd.needle, hasConfig(ConfigSmartCase));
const int oldLine = cursorLine() - cursorLineOnScreen();
2008-12-25 13:20:09 +01:00
int startPos = m_searchStartPosition + (sd.forward ? 1 : -1);
int repeat = count();
QTextCursor tc = document()->find(needleExp, startPos, flags);
while (!tc.isNull() && --repeat >= 1)
tc = document()->find(needleExp, tc, flags);
if (tc.isNull()) {
if (hasConfig(ConfigWrapScan)) {
int startPos = sd.forward ? 0 : lastPositionInDocument();
tc = document()->find(needleExp, startPos, flags);
while (!tc.isNull() && --repeat >= 1)
tc = document()->find(needleExp, tc, flags);
if (tc.isNull()) {
if (showMessages) {
showMessage(MessageError,
FakeVimHandler::tr("Pattern not found: %1").arg(sd.needle));
}
} else if (showMessages) {
QString msg = sd.forward
? FakeVimHandler::tr("search hit BOTTOM, continuing at TOP")
: FakeVimHandler::tr("search hit TOP, continuing at BOTTOM");
showMessage(MessageWarning, msg);
}
} else if (showMessages) {
QString msg = sd.forward
? FakeVimHandler::tr("search hit BOTTOM without match for: %1")
: FakeVimHandler::tr("search hit TOP without match for: %1");
showMessage(MessageError, msg.arg(sd.needle));
2009-03-20 09:26:34 +01:00
}
}
if (tc.isNull()) {
tc = cursor();
tc.setPosition(m_searchStartPosition);
if (!needleExp.isValid() && showMessages) {
QString error = needleExp.errorString();
showMessage(MessageError,
FakeVimHandler::tr("Invalid regular expression: %1").arg(error));
}
}
recordJump();
if (isVisualMode()) {
int d = tc.anchor() - tc.position();
setPosition(tc.position() + d);
} else {
// Set Cursor. In contrast to the main editor we have the cursor
// position before the anchor position.
setAnchorAndPosition(tc.position(), tc.anchor());
}
// Making this unconditional feels better, but is not "vim like".
if (oldLine != cursorLine() - cursorLineOnScreen())
scrollToLine(cursorLine() - linesOnScreen() / 2);
m_searchCursor = cursor();
2010-09-14 14:04:13 +02:00
setTargetColumn();
if (sd.highlightMatches)
highlightMatches(needleExp.pattern());
}
void FakeVimHandler::Private::searchNext(bool forward)
{
SearchData sd;
sd.needle = m_lastSearch;
sd.forward = forward ? m_lastSearchForward : !m_lastSearchForward;
sd.highlightMatches = true;
m_searchStartPosition = position();
showMessage(MessageCommand, (m_lastSearchForward ? '/' : '?') + sd.needle);
search(sd);
}
void FakeVimHandler::Private::highlightMatches(const QString &needle)
2009-03-12 18:05:36 +01:00
{
if (!hasConfig(ConfigHlSearch) || needle == m_oldNeedle)
2009-03-12 18:05:36 +01:00
return;
m_oldNeedle = needle;
updateHighlights();
2009-03-12 18:05:36 +01:00
}
2008-12-19 14:35:57 +01:00
void FakeVimHandler::Private::moveToFirstNonBlankOnLine()
{
QTextCursor tc2 = cursor();
moveToFirstNonBlankOnLine(&tc2);
setPosition(tc2.position());
}
void FakeVimHandler::Private::moveToFirstNonBlankOnLine(QTextCursor *tc)
{
QTextDocument *doc = tc->document();
int firstPos = tc->block().position();
2010-09-13 15:23:20 +02:00
for (int i = firstPos, n = firstPos + block().length(); i < n; ++i) {
if (!doc->characterAt(i).isSpace() || i == n - 1) {
tc->setPosition(i);
2008-12-19 14:35:57 +01:00
return;
}
}
tc->setPosition(block().position());
2008-12-19 14:35:57 +01:00
}
void FakeVimHandler::Private::indentSelectedText(QChar typedChar)
{
beginEditBlock();
setTargetColumn();
int beginLine = qMin(lineForPosition(position()), lineForPosition(anchor()));
int endLine = qMax(lineForPosition(position()), lineForPosition(anchor()));
Range range(anchor(), position(), m_rangemode);
indentText(range, typedChar);
setPosition(firstPositionInLine(beginLine));
handleStartOfLine();
setTargetColumn();
setDotCommand("%1==", endLine - beginLine + 1);
endEditBlock();
}
void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar)
{
int beginLine = lineForPosition(range.beginPos);
int endLine = lineForPosition(range.endPos);
2009-03-06 13:03:33 +01:00
if (beginLine > endLine)
qSwap(beginLine, endLine);
// LineForPosition has returned 1-based line numbers.
emit q->indentRegion(beginLine - 1, endLine - 1, typedChar);
if (beginLine != endLine)
showMessage(MessageError, "MARKS ARE OFF NOW");
}
bool FakeVimHandler::Private::isElectricCharacter(QChar c) const
{
bool result = false;
emit q->checkForElectricCharacter(&result, c);
return result;
}
void FakeVimHandler::Private::shiftRegionRight(int repeat)
{
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
int targetPos = anchor();
if (beginLine > endLine) {
qSwap(beginLine, endLine);
targetPos = position();
}
if (hasConfig(ConfigStartOfLine))
targetPos = firstPositionInLine(beginLine);
const int sw = config(ConfigShiftWidth).toInt();
m_movetype = MoveLineWise;
beginEditBlock();
for (int line = beginLine; line <= endLine; ++line) {
QString data = lineContents(line);
const Column col = indentation(data);
data = tabExpand(col.logical + sw * repeat) + data.mid(col.physical);
setLineContents(line, data);
}
endEditBlock();
2009-03-06 13:03:33 +01:00
setPosition(targetPos);
handleStartOfLine();
setTargetColumn();
setDotCommand("%1>>", endLine - beginLine + 1);
}
void FakeVimHandler::Private::shiftRegionLeft(int repeat)
{
int beginLine = lineForPosition(anchor());
int endLine = lineForPosition(position());
int targetPos = anchor();
if (beginLine > endLine) {
qSwap(beginLine, endLine);
targetPos = position();
}
2010-02-08 13:31:59 +01:00
const int shift = config(ConfigShiftWidth).toInt() * repeat;
const int tab = config(ConfigTabStop).toInt();
if (hasConfig(ConfigStartOfLine))
targetPos = firstPositionInLine(beginLine);
m_movetype = MoveLineWise;
beginEditBlock();
2010-02-08 13:31:59 +01:00
for (int line = endLine; line >= beginLine; --line) {
int pos = firstPositionInLine(line);
2010-02-08 13:31:59 +01:00
const QString text = lineContents(line);
int amount = 0;
int i = 0;
2010-02-08 13:31:59 +01:00
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;
}
2010-02-08 13:31:59 +01:00
removeText(Range(pos, pos + i));
setPosition(pos);
}
endEditBlock();
2009-03-06 13:03:33 +01:00
setPosition(targetPos);
handleStartOfLine();
setTargetColumn();
setDotCommand("%1<<", endLine - beginLine + 1);
}
void FakeVimHandler::Private::moveToTargetColumn()
{
2010-09-13 15:23:20 +02:00
const QTextBlock &bl = block();
//Column column = cursorColumn();
//int logical = logical
2010-09-13 15:23:20 +02:00
const int maxcol = bl.length() - 2;
if (m_targetColumn == -1) {
2010-09-14 16:58:31 +02:00
setPosition(bl.position() + qMax(0, maxcol));
return;
}
2010-09-13 15:23:20 +02:00
const int physical = logicalToPhysicalColumn(m_targetColumn, bl.text());
//qDebug() << "CORRECTING COLUMN FROM: " << logical << "TO" << m_targetColumn;
if (physical >= maxcol)
2010-09-14 16:58:31 +02:00
setPosition(bl.position() + qMax(0, maxcol));
else
2010-09-14 16:58:31 +02:00
setPosition(bl.position() + physical);
}
/* 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
*/
2010-04-28 16:19:51 +02:00
int FakeVimHandler::Private::charClass(QChar c, bool simple) const
{
if (simple)
return c.isSpace() ? 0 : 1;
2010-04-28 16:19:51 +02:00
// FIXME: This means that only characters < 256 in the
// ConfigIsKeyword setting are handled properly.
if (c.unicode() < 256) {
//int old = (c.isLetterOrNumber() || c.unicode() == '_') ? 2
// : c.isSpace() ? 0 : 1;
//qDebug() << c.unicode() << old << m_charClass[c.unicode()];
return m_charClass[c.unicode()];
}
if (c.isLetterOrNumber() || c.unicode() == '_')
return 2;
return c.isSpace() ? 0 : 1;
}
void FakeVimHandler::Private::miniBufferTextEdited(const QString &text, int cursorPos)
{
if (m_subsubmode != SearchSubSubMode && m_mode != ExMode) {
editor()->setFocus();
} else if (text.isEmpty()) {
// editing cancelled
handleDefaultKey(Input(Qt::Key_Escape, Qt::NoModifier, QString()));
editor()->setFocus();
updateCursorShape();
} else {
CommandBuffer &cmdBuf = (m_mode == ExMode) ? g.commandBuffer : g.searchBuffer;
// prepend prompt character if missing
if (!text.startsWith(cmdBuf.prompt())) {
emit q->commandBufferChanged(cmdBuf.prompt() + text, cmdBuf.cursorPos() + 1, 0, q);
cmdBuf.setContents(text, cursorPos - 1);
} else {
cmdBuf.setContents(text.mid(1), cursorPos - 1);
}
// update search expression
if (m_subsubmode == SearchSubSubMode) {
updateFind(false);
exportSelection();
}
}
}
2010-04-28 16:19:51 +02:00
// Helper to parse a-z,A-Z,48-57,_
static int someInt(const QString &str)
{
if (str.toInt())
return str.toInt();
if (str.size())
return str.at(0).unicode();
return 0;
}
void FakeVimHandler::Private::setupCharClass()
{
for (int i = 0; i < 256; ++i) {
const QChar c = QChar(QLatin1Char(i));
m_charClass[i] = c.isSpace() ? 0 : 1;
}
const QString conf = config(ConfigIsKeyword).toString();
foreach (const QString &part, conf.split(QLatin1Char(','))) {
if (part.contains(QLatin1Char('-'))) {
const int from = someInt(part.section(QLatin1Char('-'), 0, 0));
const int to = someInt(part.section(QLatin1Char('-'), 1, 1));
for (int i = qMax(0, from); i <= qMin(255, to); ++i)
m_charClass[i] = 2;
} else {
m_charClass[qMin(255, someInt(part))] = 2;
}
}
}
void FakeVimHandler::Private::moveToBoundary(bool simple, bool forward)
2008-12-25 19:50:14 +01:00
{
QTextDocument *doc = document();
QTextCursor tc(doc);
tc.setPosition(position());
if (forward ? tc.atBlockEnd() : tc.atBlockStart())
return;
QChar c = document()->characterAt(tc.position() + (forward ? -1 : 1));
int lastClass = tc.atStart() ? -1 : charClass(c, simple);
QTextCursor::MoveOperation op = forward ? Right : Left;
while (true) {
c = doc->characterAt(tc.position());
2008-12-25 19:50:14 +01:00
int thisClass = charClass(c, simple);
if (thisClass != lastClass || (forward ? tc.atBlockEnd() : tc.atBlockStart())) {
if (tc != cursor())
tc.movePosition(forward ? Left : Right);
2008-12-25 20:12:17 +01:00
break;
}
2008-12-25 19:50:14 +01:00
lastClass = thisClass;
tc.movePosition(op);
}
setPosition(tc.position());
}
void FakeVimHandler::Private::moveToNextBoundary(bool end, int count, bool simple, bool forward)
{
int repeat = count;
while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
setPosition(position() + (forward ? 1 : -1));
moveToBoundary(simple, forward);
if (atBoundary(end, simple))
--repeat;
}
}
void FakeVimHandler::Private::moveToNextBoundaryStart(int count, bool simple, bool forward)
{
moveToNextBoundary(false, count, simple, forward);
}
void FakeVimHandler::Private::moveToNextBoundaryEnd(int count, bool simple, bool forward)
{
moveToNextBoundary(true, count, simple, forward);
}
void FakeVimHandler::Private::moveToBoundaryStart(int count, bool simple, bool forward)
{
moveToNextBoundaryStart(atBoundary(false, simple) ? count - 1 : count, simple, forward);
}
void FakeVimHandler::Private::moveToBoundaryEnd(int count, bool simple, bool forward)
{
moveToNextBoundaryEnd(atBoundary(true, simple) ? count - 1 : count, simple, forward);
}
void FakeVimHandler::Private::moveToNextWord(bool end, int count, bool simple, bool forward, bool emptyLines)
{
int repeat = count;
while (repeat > 0 && !(forward ? atDocumentEnd() : atDocumentStart())) {
setPosition(position() + (forward ? 1 : -1));
moveToBoundary(simple, forward);
if (atWordBoundary(end, simple) && (emptyLines || !atEmptyLine()) )
--repeat;
2008-12-25 19:50:14 +01:00
}
}
void FakeVimHandler::Private::moveToNextWordStart(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWord(false, count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToNextWordEnd(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWord(true, count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToWordStart(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWordStart(atWordStart(simple) ? count - 1 : count, simple, forward, emptyLines);
}
void FakeVimHandler::Private::moveToWordEnd(int count, bool simple, bool forward, bool emptyLines)
{
moveToNextWordEnd(atWordEnd(simple) ? count - 1 : count, simple, forward, emptyLines);
}
bool FakeVimHandler::Private::handleFfTt(QString key)
2008-12-25 19:50:14 +01:00
{
int key0 = key.size() == 1 ? key.at(0).unicode() : 0;
int oldPos = position();
2008-12-26 00:18:03 +01:00
// m_subsubmode \in { 'f', 'F', 't', 'T' }
bool forward = m_subsubdata.is('f') || m_subsubdata.is('t');
2008-12-26 00:18:03 +01:00
int repeat = count();
QTextDocument *doc = document();
2010-09-14 16:58:31 +02:00
int n = block().position();
2008-12-26 00:18:03 +01:00
if (forward)
2010-09-14 16:58:31 +02:00
n += block().length();
2010-09-13 15:23:20 +02:00
int pos = position();
while (pos != n) {
2008-12-26 00:18:03 +01:00
pos += forward ? 1 : -1;
if (pos == n)
break;
int uc = doc->characterAt(pos).unicode();
if (uc == ParagraphSeparator)
break;
if (uc == key0)
2008-12-25 19:50:14 +01:00
--repeat;
2008-12-25 20:12:17 +01:00
if (repeat == 0) {
if (m_subsubdata.is('t'))
2008-12-26 00:18:03 +01:00
--pos;
else if (m_subsubdata.is('T'))
2008-12-26 00:18:03 +01:00
++pos;
2008-12-26 00:25:38 +01:00
2008-12-26 00:18:03 +01:00
if (forward)
2010-09-14 16:58:31 +02:00
moveRight(pos - position());
2008-12-26 00:18:03 +01:00
else
2010-09-14 16:58:31 +02:00
moveLeft(position() - pos);
2008-12-25 20:12:17 +01:00
break;
2008-12-25 19:50:14 +01:00
}
}
if (repeat == 0) {
setTargetColumn();
return true;
}
2010-09-14 16:58:31 +02:00
setPosition(oldPos);
2010-09-13 15:23:20 +02:00
return false;
2008-12-25 19:50:14 +01:00
}
void FakeVimHandler::Private::moveToMatchingParanthesis()
{
bool moved = false;
bool forward = false;
2010-09-27 15:25:18 +02:00
const int anc = anchor();
2010-09-14 14:04:13 +02:00
QTextCursor tc = cursor();
emit q->moveToMatchingParenthesis(&moved, &forward, &tc);
if (moved && forward)
tc.movePosition(Left, KeepAnchor, 1);
2010-09-27 15:25:18 +02:00
setAnchorAndPosition(anc, tc.position());
setTargetColumn();
}
int FakeVimHandler::Private::cursorLineOnScreen() const
{
2009-01-06 11:50:30 +01:00
if (!editor())
return 0;
2009-01-06 11:11:31 +01:00
QRect rect = EDITOR(cursorRect());
return rect.y() / rect.height();
}
int FakeVimHandler::Private::linesOnScreen() const
{
2009-01-06 11:50:30 +01:00
if (!editor())
return 1;
2009-01-06 11:11:31 +01:00
QRect rect = EDITOR(cursorRect());
return EDITOR(height()) / rect.height();
}
2008-12-27 21:01:05 +01:00
int FakeVimHandler::Private::columnsOnScreen() const
{
2009-01-06 11:50:30 +01:00
if (!editor())
return 1;
2009-01-06 11:11:31 +01:00
QRect rect = EDITOR(cursorRect());
// qDebug() << "WID: " << EDITOR(width()) << "RECT: " << rect;
2009-01-06 11:11:31 +01:00
return EDITOR(width()) / rect.width();
2008-12-27 21:01:05 +01:00
}
int FakeVimHandler::Private::cursorLine() const
{
2010-09-13 15:23:20 +02:00
return block().blockNumber();
}
int FakeVimHandler::Private::physicalCursorColumn() const
2008-12-27 21:01:05 +01:00
{
2010-09-13 15:23:20 +02:00
return position() - block().position();
2008-12-27 21:01:05 +01:00
}
int FakeVimHandler::Private::physicalToLogicalColumn
(const int physical, const QString &line) const
{
const int ts = config(ConfigTabStop).toInt();
int p = 0;
int logical = 0;
while (p < physical) {
QChar c = line.at(p);
//if (c == QLatin1Char(' '))
// ++logical;
//else
if (c == QLatin1Char('\t'))
logical += ts - logical % ts;
else
++logical;
//break;
++p;
}
return logical;
}
int FakeVimHandler::Private::logicalToPhysicalColumn
(const int logical, const QString &line) const
{
const int ts = config(ConfigTabStop).toInt();
int physical = 0;
for (int l = 0; l < logical && physical < line.size(); ++physical) {
QChar c = line.at(physical);
if (c == QLatin1Char('\t'))
l += ts - l % ts;
else
++l;
}
return physical;
}
int FakeVimHandler::Private::logicalCursorColumn() const
{
const int physical = physicalCursorColumn();
2010-09-13 15:23:20 +02:00
const QString line = block().text();
return physicalToLogicalColumn(physical, line);
}
Column FakeVimHandler::Private::cursorColumn() const
{
return Column(physicalCursorColumn(), logicalCursorColumn());
}
2008-12-28 02:15:26 +01:00
int FakeVimHandler::Private::linesInDocument() const
{
2010-09-13 15:23:20 +02:00
if (cursor().isNull())
return 0;
const int count = document()->blockCount();
// Qt inserts an empty line if the last character is a '\n',
// but that's not how vi does it.
return document()->lastBlock().length() <= 1 ? count - 1 : count;
2008-12-28 02:15:26 +01:00
}
void FakeVimHandler::Private::scrollToLine(int line)
{
2009-01-06 11:11:31 +01:00
// FIXME: works only for QPlainTextEdit
QScrollBar *scrollBar = EDITOR(verticalScrollBar());
2009-03-20 08:44:52 +01:00
//qDebug() << "SCROLL: " << scrollBar->value() << line;
2009-01-06 11:11:31 +01:00
scrollBar->setValue(line);
//QTC_CHECK(firstVisibleLine() == line);
}
int FakeVimHandler::Private::firstVisibleLine() const
{
QScrollBar *scrollBar = EDITOR(verticalScrollBar());
if (0 && scrollBar->value() != cursorLine() - cursorLineOnScreen()) {
qDebug() << "SCROLLBAR: " << scrollBar->value()
<< "CURSORLINE IN DOC" << cursorLine()
<< "CURSORLINE ON SCREEN" << cursorLineOnScreen();
}
//return scrollBar->value();
return cursorLine() - cursorLineOnScreen();
}
2009-03-20 08:44:52 +01:00
void FakeVimHandler::Private::scrollUp(int count)
{
scrollToLine(cursorLine() - cursorLineOnScreen() - count);
2009-03-20 08:44:52 +01:00
}
int FakeVimHandler::Private::lastPositionInDocument() const
{
QTextBlock block = document()->lastBlock();
2009-07-03 11:22:18 +02:00
return block.position() + block.length() - 1;
}
2010-05-12 11:18:18 +02:00
QString FakeVimHandler::Private::selectText(const Range &range) const
{
if (range.rangemode == RangeCharMode) {
QTextCursor tc(document());
tc.setPosition(range.beginPos, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
return tc.selection().toPlainText();
}
if (range.rangemode == RangeLineMode) {
QTextCursor tc(document());
int firstPos = firstPositionInLine(lineForPosition(range.beginPos));
int lastLine = lineForPosition(range.endPos);
bool endOfDoc = lastLine == document()->lastBlock().blockNumber() + 1;
int lastPos = endOfDoc ? lastPositionInDocument() : firstPositionInLine(lastLine + 1);
tc.setPosition(firstPos, MoveAnchor);
tc.setPosition(lastPos, KeepAnchor);
return tc.selection().toPlainText() + QString((endOfDoc? "\n" : ""));
}
// FIXME: Performance?
int beginLine = lineForPosition(range.beginPos);
int endLine = lineForPosition(range.endPos);
int beginColumn = 0;
int endColumn = INT_MAX;
if (range.rangemode == RangeBlockMode) {
2009-10-16 11:30:46 +02:00
int column1 = range.beginPos - firstPositionInLine(beginLine);
int column2 = range.endPos - firstPositionInLine(endLine);
beginColumn = qMin(column1, column2);
endColumn = qMax(column1, column2);
2009-10-16 11:30:46 +02:00
}
int len = endColumn - beginColumn + 1;
QString contents;
QTextBlock block = 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::yankText(const Range &range, int reg)
{
setRegister(reg, selectText(range), range.rangemode);
}
2010-01-21 17:42:46 +01:00
void FakeVimHandler::Private::transformText(const Range &range,
Transformation transformFunc, const QVariant &extra)
2009-01-16 16:15:01 +01:00
{
2010-09-13 15:23:20 +02:00
QTextCursor tc = cursor();
switch (range.rangemode) {
case RangeCharMode: {
2010-05-28 13:09:24 +02:00
// This can span multiple lines.
beginEditBlock();
tc.setPosition(range.beginPos, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
TransformationData td(tc.selectedText(), extra);
(this->*transformFunc)(&td);
tc.removeSelectedText();
tc.insertText(td.to);
2010-05-28 13:09:24 +02:00
endEditBlock();
return;
}
case RangeLineMode:
case RangeLineModeExclusive: {
beginEditBlock();
tc.setPosition(range.beginPos, MoveAnchor);
tc.movePosition(StartOfLine, MoveAnchor);
tc.setPosition(range.endPos, KeepAnchor);
tc.movePosition(EndOfLine, KeepAnchor);
if (range.rangemode != RangeLineModeExclusive) {
// make sure that complete lines are removed
// - also at the beginning and at the end of the document
if (tc.atEnd()) {
tc.setPosition(range.beginPos, MoveAnchor);
tc.movePosition(StartOfLine, MoveAnchor);
if (!tc.atStart()) {
// also remove first line if it is the only one
tc.movePosition(Left, MoveAnchor, 1);
tc.movePosition(EndOfLine, MoveAnchor, 1);
}
tc.setPosition(range.endPos, KeepAnchor);
tc.movePosition(EndOfLine, KeepAnchor);
} else {
tc.movePosition(Right, KeepAnchor, 1);
}
}
TransformationData td(tc.selectedText(), extra);
(this->*transformFunc)(&td);
tc.removeSelectedText();
tc.insertText(td.to);
2010-05-28 13:09:24 +02:00
endEditBlock();
return;
}
case RangeBlockAndTailMode:
case RangeBlockMode: {
int beginLine = lineForPosition(range.beginPos);
int endLine = lineForPosition(range.endPos);
2009-10-16 11:30:46 +02:00
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 = document()->findBlockByNumber(endLine - 1);
beginEditBlock();
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);
TransformationData td(tc.selectedText(), extra);
(this->*transformFunc)(&td);
tc.removeSelectedText();
tc.insertText(td.to);
block = block.previous();
}
endEditBlock();
}
}
}
2010-05-11 14:26:37 +02:00
void FakeVimHandler::Private::insertText(const Register &reg)
{
QTC_ASSERT(reg.rangemode == RangeCharMode,
qDebug() << "WRONG INSERT MODE: " << reg.rangemode; return);
2010-09-16 16:56:11 +02:00
setAnchor();
2010-09-14 16:58:31 +02:00
cursor().insertText(reg.contents);
//dump("AFTER INSERT");
2010-05-11 14:26:37 +02:00
}
void FakeVimHandler::Private::removeText(const Range &range)
{
2010-09-14 14:04:13 +02:00
//qDebug() << "REMOVE: " << range;
transformText(range, &FakeVimHandler::Private::removeTransform);
}
void FakeVimHandler::Private::removeTransform(TransformationData *td)
{
Q_UNUSED(td);
}
2010-05-12 11:18:18 +02:00
void FakeVimHandler::Private::downCase(const Range &range)
{
transformText(range, &FakeVimHandler::Private::downCaseTransform);
}
void FakeVimHandler::Private::downCaseTransform(TransformationData *td)
{
td->to = td->from.toLower();
}
2010-05-12 11:18:18 +02:00
void FakeVimHandler::Private::upCase(const Range &range)
{
transformText(range, &FakeVimHandler::Private::upCaseTransform);
}
void FakeVimHandler::Private::upCaseTransform(TransformationData *td)
{
td->to = td->from.toUpper();
}
2010-05-12 11:18:18 +02:00
void FakeVimHandler::Private::invertCase(const Range &range)
{
2010-05-12 11:18:18 +02:00
transformText(range, &FakeVimHandler::Private::invertCaseTransform);
}
void FakeVimHandler::Private::invertCaseTransform(TransformationData *td)
{
foreach (QChar c, td->from)
td->to += c.isUpper() ? c.toLower() : c.toUpper();
}
2010-05-12 11:18:18 +02:00
void FakeVimHandler::Private::replaceText(const Range &range, const QString &str)
{
Transformation tr = &FakeVimHandler::Private::replaceByStringTransform;
transformText(range, tr, str);
}
void FakeVimHandler::Private::replaceByStringTransform(TransformationData *td)
{
td->to = td->extraData.toString();
}
void FakeVimHandler::Private::replaceByCharTransform(TransformationData *td)
{
2010-05-28 13:09:24 +02:00
td->to = QString(td->from.size(), td->extraData.toChar());
}
void FakeVimHandler::Private::pasteText(bool afterCursor)
{
const QString text = registerContents(m_register);
const RangeMode rangeMode = registerRangeMode(m_register);
beginEditBlock();
// In visual mode paste text only inside selection.
bool pasteAfter = isVisualMode() ? false : afterCursor;
bool visualCharMode = isVisualCharMode();
if (visualCharMode) {
leaveVisualMode();
m_rangemode = RangeCharMode;
Range range = currentRange();
range.endPos++;
yankText(range, m_register);
removeText(range);
} else if (isVisualLineMode()) {
leaveVisualMode();
m_rangemode = RangeLineMode;
Range range = currentRange();
range.endPos++;
yankText(range, m_register);
removeText(range);
handleStartOfLine();
} else if (isVisualBlockMode()) {
leaveVisualMode();
m_rangemode = RangeBlockMode;
Range range = currentRange();
yankText(range, m_register);
removeText(range);
setPosition(qMin(position(), anchor()));
}
switch (rangeMode) {
case RangeCharMode: {
m_targetColumn = 0;
if (pasteAfter && rightDist() > 0)
moveRight();
insertText(text.repeated(count()));
if (!pasteAfter && atEndOfLine())
moveLeft();
moveLeft();
break;
}
case RangeLineMode:
case RangeLineModeExclusive: {
QTextCursor tc = cursor();
if (visualCharMode)
tc.insertBlock();
else
moveToStartOfLine();
m_targetColumn = 0;
if (pasteAfter) {
bool lastLine = document()->lastBlock() == this->block();
if (lastLine) {
tc.movePosition(EndOfLine, MoveAnchor);
tc.insertBlock();
}
moveDown();
}
const int pos = position();
insertText(text.repeated(count()));
setPosition(pos);
moveToFirstNonBlankOnLine();
break;
}
case RangeBlockAndTailMode:
case RangeBlockMode: {
const int pos = position();
if (pasteAfter && rightDist() > 0)
moveRight();
2010-09-13 15:23:20 +02:00
QTextCursor tc = cursor();
const int col = tc.columnNumber();
QTextBlock block = tc.block();
const QStringList lines = text.split(QChar('\n'));
for (int i = 0; i < lines.size() - 1; ++i) {
if (!block.isValid()) {
tc.movePosition(EndOfDocument);
tc.insertBlock();
block = tc.block();
}
// resize line
int length = block.length();
int begin = block.position();
if (col >= length) {
tc.setPosition(begin + length - 1);
tc.insertText(QString(col - length + 1, QChar(' ')));
2009-10-16 11:30:46 +02:00
} else {
tc.setPosition(begin + col);
}
// insert text
const QString line = lines.at(i).repeated(count());
tc.insertText(line);
// next line
block = block.next();
}
setPosition(pos);
if (pasteAfter)
moveRight();
break;
}
}
endEditBlock();
2009-01-16 16:15:01 +01:00
}
QString FakeVimHandler::Private::lineContents(int line) const
{
return document()->findBlockByNumber(line - 1).text();
}
2010-05-11 14:26:37 +02:00
void FakeVimHandler::Private::setLineContents(int line, const QString &contents)
{
QTextBlock block = document()->findBlockByNumber(line - 1);
2010-09-13 15:23:20 +02:00
QTextCursor tc = cursor();
const int begin = block.position();
const int len = block.length();
tc.setPosition(begin);
tc.setPosition(begin + len - 1, KeepAnchor);
tc.removeSelectedText();
tc.insertText(contents);
}
int FakeVimHandler::Private::blockBoundary(const QString &left,
const QString &right, bool closing, int count) const
{
const QString &begin = closing ? left : right;
const QString &end = closing ? right : left;
// shift cursor if it is already on opening/closing string
QTextCursor tc1 = cursor();
int pos = tc1.position();
int max = document()->characterCount();
int sz = left.size();
int from = qMax(pos - sz + 1, 0);
int to = qMin(pos + sz, max);
tc1.setPosition(from);
tc1.setPosition(to, KeepAnchor);
int i = tc1.selectedText().indexOf(left);
if (i != -1) {
// - on opening string:
tc1.setPosition(from + i + sz);
} else {
sz = right.size();
from = qMax(pos - sz + 1, 0);
to = qMin(pos + sz, max);
tc1.setPosition(from);
tc1.setPosition(to, KeepAnchor);
i = tc1.selectedText().indexOf(right);
if (i != -1) {
// - on closing string:
tc1.setPosition(from + i);
} else {
tc1 = cursor();
}
}
QTextCursor tc2 = tc1;
QTextDocument::FindFlags flags(closing ? 0 : QTextDocument::FindBackward);
int level = 0;
int counter = 0;
while (true) {
tc2 = document()->find(end, tc2, flags);
if (tc2.isNull())
return -1;
if (!tc1.isNull())
tc1 = document()->find(begin, tc1, flags);
while (!tc1.isNull() && (closing ? (tc1 < tc2) : (tc2 < tc1))) {
++level;
tc1 = document()->find(begin, tc1, flags);
}
while (level > 0
&& (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
--level;
tc2 = document()->find(end, tc2, flags);
if (tc2.isNull())
return -1;
}
if (level == 0
&& (tc1.isNull() || (closing ? (tc2 < tc1) : (tc1 < tc2)))) {
++counter;
if (counter >= count)
break;
}
}
return tc2.position() - end.size();
}
int FakeVimHandler::Private::firstPositionInLine(int line) const
2009-01-06 11:33:07 +01:00
{
return document()->findBlockByNumber(line - 1).position();
2009-01-06 11:33:07 +01:00
}
int FakeVimHandler::Private::lastPositionInLine(int line) const
{
QTextBlock block = document()->findBlockByNumber(line - 1);
return block.position() + block.length() - 1;
}
int FakeVimHandler::Private::lineForPosition(int pos) const
{
2010-09-13 15:23:20 +02:00
QTextCursor tc = cursor();
tc.setPosition(pos);
return tc.block().blockNumber() + 1;
}
void FakeVimHandler::Private::toggleVisualMode(VisualMode visualMode)
{
if (isVisualMode()) {
leaveVisualMode();
} else {
m_positionPastEnd = false;
m_anchorPastEnd = false;
m_visualMode = visualMode;
const int pos = position();
setAnchorAndPosition(pos, pos);
updateMiniBuffer();
}
}
void FakeVimHandler::Private::leaveVisualMode()
{
if (!isVisualMode())
return;
m_lastSelectionCursor = cursor();
m_lastSelectionMode = m_visualMode;
int from = m_lastSelectionCursor.anchor();
int to = m_lastSelectionCursor.position();
if (from > to)
qSwap(from, to);
setMark('<', from);
setMark('>', to);
if (isVisualLineMode())
m_movetype = MoveLineWise;
else if (isVisualCharMode())
m_movetype = MoveInclusive;
m_visualMode = NoVisualMode;
updateMiniBuffer();
}
2009-01-06 11:50:30 +01:00
QWidget *FakeVimHandler::Private::editor() const
2009-01-06 11:43:27 +01:00
{
return m_textedit
? static_cast<QWidget *>(m_textedit)
: static_cast<QWidget *>(m_plaintextedit);
}
char FakeVimHandler::Private::currentModeCode() const
{
if (m_mode == ExMode)
return 'c';
else if (isVisualMode())
return 'v';
else if (m_mode == CommandMode)
return 'n';
else
return 'i';
}
void FakeVimHandler::Private::undo()
{
2010-05-11 14:26:37 +02:00
// FIXME: That's only an approximaxtion. The real solution might
// be to store marks and old userData with QTextBlock setUserData
// and retrieve them afterward.
const int current = revision();
EDITOR(undo());
const int rev = revision();
// rewind to last saved revision
while (!m_undo.empty() && m_undo.top().revision > rev)
m_undo.pop();
if (current == rev) {
showMessage(MessageInfo, FakeVimHandler::tr("Already at oldest change"));
return;
}
clearMessage();
if (!m_undo.empty()) {
State &state = m_undo.top();
if (state.revision == rev) {
m_lastChangePosition = state.position;
m_marks = state.marks;
setPosition(m_lastChangePosition);
state.revision = current;
m_redo.push(m_undo.pop());
}
}
setTargetColumn();
if (atEndOfLine())
moveLeft();
}
void FakeVimHandler::Private::redo()
{
const int current = revision();
EDITOR(redo());
const int rev = revision();
// forward to last saved revision
while (!m_redo.empty() && m_redo.top().revision < rev)
m_redo.pop();
if (rev == current) {
showMessage(MessageInfo, FakeVimHandler::tr("Already at newest change"));
return;
}
clearMessage();
if (!m_redo.empty()) {
State &state = m_redo.top();
if (state.revision == rev) {
int pos = qMin(document()->characterCount() - 1, state.position);
if (lineForPosition(pos) != state.line)
pos = lastPositionInLine(state.line);
m_lastChangePosition = pos;
m_marks = state.marks;
setPosition(m_lastChangePosition);
state.revision = current;
m_undo.push(m_redo.pop());
}
}
setTargetColumn();
if (atEndOfLine())
moveLeft();
2009-01-16 13:10:42 +01:00
}
2010-09-13 15:23:20 +02:00
void FakeVimHandler::Private::updateCursorShape()
{
bool thinCursor = m_mode == ExMode
|| m_subsubmode == SearchSubSubMode
|| m_mode == InsertMode
|| isVisualMode()
|| cursor().hasSelection();
EDITOR(setOverwriteMode(!thinCursor));
}
void FakeVimHandler::Private::enterReplaceMode()
{
m_mode = ReplaceMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_lastInsertion.clear();
m_lastDeletion.clear();
}
void FakeVimHandler::Private::enterInsertMode()
{
m_mode = InsertMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
m_lastInsertion.clear();
m_lastDeletion.clear();
}
void FakeVimHandler::Private::enterCommandMode()
{
if (atEndOfLine())
moveLeft();
m_mode = CommandMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
}
void FakeVimHandler::Private::enterExMode()
{
m_mode = ExMode;
m_submode = NoSubMode;
m_subsubmode = NoSubSubMode;
}
void FakeVimHandler::Private::recordJump()
{
CursorPosition pos = cursorPosition();
setMark('\'', pos.position);
if (m_jumpListUndo.isEmpty() || m_jumpListUndo.top().position != pos.position)
m_jumpListUndo.push(pos);
m_jumpListRedo.clear();
UNDO_DEBUG("jumps: " << m_jumpListUndo);
}
void FakeVimHandler::Private::jump(int distance)
{
QStack<CursorPosition> &from = (distance > 0) ? m_jumpListRedo : m_jumpListUndo;
QStack<CursorPosition> &to = (distance > 0) ? m_jumpListUndo : m_jumpListRedo;
int len = qMin(qAbs(distance), from.size());
setMark('\'', position());
for (int i = 0; i < len; ++i) {
to.push(cursorPosition());
setCursorPosition(from.top());
from.pop();
}
}
Column 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 Column(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(' '));
}
2009-04-03 11:54:29 +02:00
void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown)
{
if (!hasConfig(ConfigAutoIndent) && !hasConfig(ConfigSmartIndent))
2009-04-03 11:54:29 +02:00
return;
if (hasConfig(ConfigSmartIndent)) {
2010-09-13 15:23:20 +02:00
QTextBlock bl = block();
Range range(bl.position(), bl.position());
const int oldSize = bl.text().size();
indentText(range, QLatin1Char('\n'));
2010-09-13 15:23:20 +02:00
m_justAutoIndented = bl.text().size() - oldSize;
} else {
2010-09-13 15:23:20 +02:00
QTextBlock bl = goingDown ? block().previous() : block().next();
QString text = bl.text();
int pos = 0;
int n = text.size();
while (pos < n && text.at(pos).isSpace())
++pos;
text.truncate(pos);
// FIXME: handle 'smartindent' and 'cindent'
2010-05-11 14:26:37 +02:00
insertText(text);
m_justAutoIndented = text.size();
}
2009-04-03 11:54:29 +02:00
}
bool FakeVimHandler::Private::removeAutomaticIndentation()
{
if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
return false;
2010-09-13 15:23:20 +02:00
/*
2009-04-03 11:54:29 +02:00
m_tc.movePosition(StartOfLine, KeepAnchor);
m_tc.removeSelectedText();
m_lastInsertion.chop(m_justAutoIndented);
2010-09-13 15:23:20 +02:00
*/
2009-04-03 11:54:29 +02:00
m_justAutoIndented = 0;
return true;
}
void FakeVimHandler::Private::handleStartOfLine()
{
if (hasConfig(ConfigStartOfLine))
moveToFirstNonBlankOnLine();
}
2009-04-08 16:05:24 +02:00
void FakeVimHandler::Private::replay(const QString &command, int n)
2009-04-06 10:58:48 +02:00
{
//qDebug() << "REPLAY: " << quoteUnprintable(command);
for (int i = n; --i >= 0; ) {
foreach (QChar c, command) {
//qDebug() << " REPLAY: " << c.unicode();
handleDefaultKey(Input(c));
}
}
2009-04-06 10:58:48 +02:00
}
void FakeVimHandler::Private::selectTextObject(bool simple, bool inner)
{
bool setupAnchor = (position() == anchor());
// set anchor if not already set
if (setupAnchor) {
moveToBoundaryStart(1, simple, false);
setAnchor();
} else {
moveRight();
if (atEndOfLine())
moveRight();
}
const int repeat = count();
if (inner) {
moveToBoundaryEnd(repeat, simple);
} else {
for (int i = 0; i < repeat; ++i) {
// select leading spaces
bool leadingSpace = characterAtCursor().isSpace();
if (leadingSpace)
moveToNextBoundaryStart(1, simple);
// select word
moveToWordEnd(1, simple);
// select trailing spaces if no leading space
if (!leadingSpace && document()->characterAt(position() + 1).isSpace()
&& !atBlockStart()) {
moveToNextBoundaryEnd(1, simple);
}
// if there are no trailing spaces in selection select all leading spaces
// after previous character
if (setupAnchor && (!characterAtCursor().isSpace() || atBlockEnd())) {
int min = block().position();
int pos = anchor();
while (pos >= min && document()->characterAt(--pos).isSpace()) {}
if (pos >= min)
setAnchorAndPosition(pos + 1, position());
}
if (i + 1 < repeat) {
moveRight();
if (atEndOfLine())
moveRight();
}
}
}
if (inner) {
m_movetype = MoveInclusive;
} else {
m_movetype = MoveExclusive;
moveRight();
if (atEndOfLine())
moveRight();
}
setTargetColumn();
}
void FakeVimHandler::Private::selectWordTextObject(bool inner)
{
selectTextObject(false, inner);
}
void FakeVimHandler::Private::selectWORDTextObject(bool inner)
{
selectTextObject(true, inner);
}
void FakeVimHandler::Private::selectSentenceTextObject(bool inner)
{
Q_UNUSED(inner);
}
void FakeVimHandler::Private::selectParagraphTextObject(bool inner)
{
Q_UNUSED(inner);
}
void FakeVimHandler::Private::selectBlockTextObject(bool inner,
char left, char right)
{
QString sleft = QString(QLatin1Char(left));
QString sright = QString(QLatin1Char(right));
int p1 = blockBoundary(sleft, sright, false, count());
if (p1 == -1)
return;
int p2 = blockBoundary(sleft, sright, true, count());
if (p2 == -1)
return;
if (inner) {
p1 += sleft.size();
--p2;
} else {
p2 -= sright.size() - 1;
}
setAnchorAndPosition(p1, p2);
m_movetype = MoveInclusive;
}
static bool isSign(const QChar c)
{
return c.unicode() == '-' || c.unicode() == '+';
}
void FakeVimHandler::Private::changeNumberTextObject(bool doIncrement)
{
QTextCursor tc = cursor();
int pos = tc.position();
const int n = lastPositionInDocument();
QTextDocument *doc = document();
QChar c = doc->characterAt(pos);
if (!c.isNumber()) {
if (pos == n || !isSign(c))
return;
++pos;
c = doc->characterAt(pos);
if (!c.isNumber())
return;
}
int p1 = pos;
while (p1 >= 1 && doc->characterAt(p1 - 1).isNumber())
--p1;
if (p1 >= 1 && isSign(doc->characterAt(p1 - 1)))
--p1;
int p2 = pos;
while (p2 <= n - 1 && doc->characterAt(p2 + 1).isNumber())
++p2;
++p2;
setAnchorAndPosition(p2, p1);
QString orig = selectText(currentRange());
int value = orig.toInt();
value = doIncrement ? value + 1 : value - 1;
QString repl = QString::fromLatin1("%1").arg(value, orig.size(), 10, QLatin1Char('0'));
replaceText(currentRange(), repl);
moveLeft();
}
void FakeVimHandler::Private::selectQuotedStringTextObject(bool inner,
const QString &quote)
{
QTextCursor tc = cursor();
int sz = quote.size();
QTextCursor tc1;
QTextCursor tc2(document());
while (tc2 <= tc) {
tc1 = document()->find(quote, tc2);
if (tc1.isNull() || tc1.anchor() > tc.position())
return;
tc2 = document()->find(quote, tc1);
if (tc2.isNull())
return;
}
int p1 = tc1.position();
int p2 = tc2.position();
if (inner) {
p2 -= sz + 1;
if (document()->characterAt(p1) == ParagraphSeparator)
++p1;
} else {
p1 -= sz;
p2 -= sz;
}
setAnchorAndPosition(p1, p2);
m_movetype = MoveInclusive;
}
2010-05-11 14:26:37 +02:00
int FakeVimHandler::Private::mark(int code) const
{
// FIXME: distinguish local and global marks.
//qDebug() << "MARK: " << code << m_marks.value(code, -1) << m_marks;
2010-09-14 16:58:31 +02:00
if (isVisualMode()) {
if (code == '<')
return position();
if (code == '>')
return anchor();
}
if (code == '.')
return m_lastChangePosition;
QTextCursor tc = m_marks.value(code);
return tc.isNull() ? -1 : tc.position();
2010-05-11 14:26:37 +02:00
}
void FakeVimHandler::Private::setMark(int code, int position)
{
// FIXME: distinguish local and global marks.
QTextCursor tc = cursor();
tc.setPosition(position, MoveAnchor);
m_marks[code] = tc;
2010-05-11 14:26:37 +02:00
}
RangeMode FakeVimHandler::Private::registerRangeMode(int reg) const
{
bool isClipboard;
bool isSelection;
getRegisterType(reg, &isClipboard, &isSelection);
if (isClipboard || isSelection) {
QClipboard *clipboard = QApplication::clipboard();
QClipboard::Mode mode = isClipboard ? QClipboard::Clipboard : QClipboard::Selection;
// Use range mode from Vim's clipboard data if available.
const QMimeData *data = clipboard->mimeData(mode);
if (data != 0 && data->hasFormat(vimMimeText)) {
QByteArray bytes = data->data(vimMimeText);
if (bytes.length() > 0)
return static_cast<RangeMode>(bytes.at(0));
}
// If register content is clipboard:
// - return RangeLineMode if text ends with new line char,
// - return RangeCharMode otherwise.
QString text = clipboard->text(mode);
return (text.endsWith('\n') || text.endsWith('\r')) ? RangeLineMode : RangeCharMode;
}
return g.registers[reg].rangemode;
}
void FakeVimHandler::Private::setRegister(int reg, const QString &contents, RangeMode mode)
{
bool copyToClipboard;
bool copyToSelection;
getRegisterType(reg, &copyToClipboard, &copyToSelection);
if (copyToClipboard || copyToSelection) {
if (copyToClipboard)
setClipboardData(contents, mode, QClipboard::Clipboard);
if (copyToSelection)
setClipboardData(contents, mode, QClipboard::Selection);
} else {
g.registers[reg].contents = contents;
g.registers[reg].rangemode = mode;
}
}
QString FakeVimHandler::Private::registerContents(int reg) const
{
bool copyFromClipboard;
bool copyFromSelection;
getRegisterType(reg, &copyFromClipboard, &copyFromSelection);
if (copyFromClipboard || copyFromSelection) {
QClipboard *clipboard = QApplication::clipboard();
if (copyFromClipboard)
return clipboard->text(QClipboard::Clipboard);
if (copyFromSelection)
return clipboard->text(QClipboard::Selection);
}
return g.registers[reg].contents;
}
void FakeVimHandler::Private::getRegisterType(int reg, bool *isClipboard, bool *isSelection) const
{
bool clipboard = false;
bool selection = false;
if (reg == '"') {
QStringList list = config(ConfigClipboard).toString().split(',');
clipboard = list.contains(QString("unnamedplus"));
selection = list.contains(QString("unnamed"));
} else if (reg == '+') {
clipboard = true;
} else if (reg == '*') {
selection = true;
}
// selection (primary) is clipboard on systems without selection support
if (selection && !QApplication::clipboard()->supportsSelection()) {
clipboard = true;
selection = false;
}
if (isClipboard != 0)
*isClipboard = clipboard;
if (isSelection != 0)
*isSelection = selection;
}
///////////////////////////////////////////////////////////////////////
//
// FakeVimHandler
//
///////////////////////////////////////////////////////////////////////
FakeVimHandler::FakeVimHandler(QWidget *widget, QObject *parent)
: QObject(parent), d(new Private(this, widget))
{}
FakeVimHandler::~FakeVimHandler()
{
delete d;
}
// gracefully handle that the parent editor is deleted
void FakeVimHandler::disconnectFromEditor()
{
d->m_textedit = 0;
d->m_plaintextedit = 0;
}
bool FakeVimHandler::eventFilter(QObject *ob, QEvent *ev)
{
bool active = theFakeVimSetting(ConfigUseFakeVim)->value().toBool();
// Catch mouse events on the viewport.
2010-05-10 08:40:15 +02:00
QWidget *viewport = 0;
if (d->m_plaintextedit)
viewport = d->m_plaintextedit->viewport();
else if (d->m_textedit)
viewport = d->m_textedit->viewport();
if (ob == viewport) {
if (active && ev->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
if (mev->button() == Qt::LeftButton) {
d->importSelection();
//return true;
}
}
if (active && ev->type() == QEvent::MouseButtonPress) {
QMouseEvent *mev = static_cast<QMouseEvent *>(ev);
if (mev->button() == Qt::LeftButton) {
d->m_visualMode = NoVisualMode;
}
}
return QObject::eventFilter(ob, ev);
}
if (active && ev->type() == QEvent::Shortcut) {
d->passShortcuts(false);
return false;
}
2010-04-15 13:29:12 +02:00
if (active && ev->type() == QEvent::InputMethod && ob == d->editor()) {
// This handles simple dead keys. The sequence of events is
// KeyRelease-InputMethod-KeyRelease for dead keys instead of
// KeyPress-KeyRelease as for simple keys. As vi acts on key presses,
// we have to act on the InputMethod event.
// FIXME: A first approximation working for e.g. ^ on a German keyboard
QInputMethodEvent *imev = static_cast<QInputMethodEvent *>(ev);
KEY_DEBUG("INPUTMETHOD" << imev->commitString() << imev->preeditString());
QString commitString = imev->commitString();
int key = commitString.size() == 1 ? commitString.at(0).unicode() : 0;
QKeyEvent kev(QEvent::KeyPress, key, Qt::KeyboardModifiers(), commitString);
EventResult res = d->handleEvent(&kev);
return res == EventHandled;
}
if (active && ev->type() == QEvent::KeyPress) {
QKeyEvent *kev = static_cast<QKeyEvent *>(ev);
KEY_DEBUG("KEYPRESS" << kev->key() << kev->text() << QChar(kev->key()));
EventResult res = d->handleEvent(kev);
//if (d->m_mode == InsertMode)
// emit completionRequested();
// 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;
}
if (active && ev->type() == QEvent::FocusIn && ob == d->editor()) {
d->stopIncrementalFind();
}
return QObject::eventFilter(ob, ev);
}
void FakeVimHandler::installEventFilter()
{
d->installEventFilter();
}
void FakeVimHandler::setupWidget()
{
d->setupWidget();
}
void FakeVimHandler::restoreWidget(int tabSize)
{
d->restoreWidget(tabSize);
}
void FakeVimHandler::handleCommand(const QString &cmd)
{
2009-04-06 10:58:48 +02:00
d->handleCommand(cmd);
}
2011-02-18 15:31:31 +01:00
void FakeVimHandler::handleReplay(const QString &keys)
{
d->replay(keys, 1);
}
void FakeVimHandler::handleInput(const QString &keys)
{
Mode oldMode = d->m_mode;
d->m_mode = CommandMode;
Inputs inputs(keys);
foreach (const Input &input, inputs)
d->handleKey(input);
d->m_mode = oldMode;
}
void FakeVimHandler::setCurrentFileName(const QString &fileName)
{
d->m_currentFileName = fileName;
}
QString FakeVimHandler::currentFileName() const
{
return d->m_currentFileName;
}
void FakeVimHandler::showMessage(MessageLevel level, const QString &msg)
{
d->showMessage(level, msg);
}
QWidget *FakeVimHandler::widget()
{
return d->editor();
}
// Test only
int FakeVimHandler::physicalIndentation(const QString &line) const
{
Column ind = d->indentation(line);
return ind.physical;
}
int FakeVimHandler::logicalIndentation(const QString &line) const
{
Column ind = d->indentation(line);
return ind.logical;
}
QString FakeVimHandler::tabExpand(int n) const
{
return d->tabExpand(n);
}
void FakeVimHandler::miniBufferTextEdited(const QString &text, int cursorPos)
{
d->miniBufferTextEdited(text, cursorPos);
}
2009-03-30 12:40:08 +02:00
} // namespace Internal
} // namespace FakeVim
2010-03-26 13:22:06 +01:00
#include "fakevimhandler.moc"