FakeVim: Monitor inserted text using signals from QTextDocument

Use QTextDocument::onContentsChanged() to monitor text inserted and
removed in insert mode for dot command (or just repeat with 2i, 3o etc.)
to re-insert same text.

Works even if auto-completion is replacing '.' with '->' for pointers.

Change-Id: Ie39edcdc9ec60bcf6c7d10f021248c3a0aee76b6
Reviewed-by: hjk <hjk121@nokiamail.com>
This commit is contained in:
hluk
2013-07-09 19:39:31 +02:00
committed by hjk
parent a43ef07a6b
commit e5c323e83a
2 changed files with 300 additions and 272 deletions

View File

@@ -3292,8 +3292,16 @@ void FakeVimPlugin::test_vim_qtcreator()
""); "");
// Record long insert mode. // Record long insert mode.
KEYS("qb" "4s" "bool" "<down>" "Q_<insert>ASSERT" "<down><down>" "<insert><bs>2" KEYS("qb"
"<c-o>2w<delete>1" "<c-o>:s/true/false<cr><esc>" "q", "4s" "bool" // 1
"<down>"
"Q_<insert>ASSERT" // 2
"<down><down>"
"<insert><bs>2" // 3
"<c-o>2w"
"<delete>1" // 4
"<c-o>:s/true/false<cr><esc>" // 5
"q",
"bool f(int arg1, int arg2 = 0) {" N "bool f(int arg1, int arg2 = 0) {" N
" Q_ASSERT(arg1 >= 0);" N " Q_ASSERT(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
@@ -3301,35 +3309,35 @@ void FakeVimPlugin::test_vim_qtcreator()
"}" N "}" N
""); "");
KEYS("u", KEYS("u", // 5
"bool f(int arg1, int arg2 = 0) {" N "bool f(int arg1, int arg2 = 0) {" N
" Q_ASSERT(arg1 >= 0);" N " Q_ASSERT(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
X " if (arg2 > 1) return true;" N X " if (arg2 > 1) return true;" N
"}" N "}" N
""); "");
KEYS("u", KEYS("u", // 4
"bool f(int arg1, int arg2 = 0) {" N "bool f(int arg1, int arg2 = 0) {" N
" Q_ASSERT(arg1 >= 0);" N " Q_ASSERT(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
" if (arg2 > " X "0) return true;" N " if (arg2 > " X "0) return true;" N
"}" N "}" N
""); "");
KEYS("u", KEYS("u", // 3
"bool f(int arg1, int arg2 = 0) {" N "bool f(int arg1, int arg2 = 0) {" N
" Q_ASSERT(arg1 >= 0);" N " Q_ASSERT(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
" if (arg1" X " > 0) return true;" N " if (arg1" X " > 0) return true;" N
"}" N "}" N
""); "");
KEYS("u", KEYS("u", // 2
"bool f(int arg1, int arg2 = 0) {" N "bool f(int arg1, int arg2 = 0) {" N
" " X "assert(arg1 >= 0);" N " " X "assert(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
"}" N "}" N
""); "");
KEYS("u", KEYS("u", // 1
X "void f(int arg1, int arg2 = 0) {" N X "void f(int arg1, int arg2 = 0) {" N
" assert(arg1 >= 0);" N " assert(arg1 >= 0);" N
" if (arg1 > 0) return true;" N " if (arg1 > 0) return true;" N
@@ -3374,4 +3382,28 @@ void FakeVimPlugin::test_vim_qtcreator()
" if (arg2 > 1) return false;" N " if (arg2 > 1) return false;" N
"}" N "}" N
""); "");
// Macros
data.setText(
"void f(int arg1) {" N
"}" N
"");
KEYS("2o" "#ifdef HAS_FEATURE<cr>doSomething();<cr>"
"#else<cr>"
"doSomethingElse<bs><bs><bs><bs>2();<cr>"
"#endif"
"<esc>",
"void f(int arg1) {" N
"#ifdef HAS_FEATURE" N
" doSomething();" N
"#else" N
" doSomething2();" N
"#endif" N
"#ifdef HAS_FEATURE" N
" doSomething();" N
"#else" N
" doSomething2();" N
"#endi" X "f" N
"}" N
"");
} }

View File

@@ -1458,8 +1458,9 @@ public:
void clearPendingInput(); void clearPendingInput();
void waitForMapping(); void waitForMapping();
EventResult stopWaitForMapping(bool hasInput); EventResult stopWaitForMapping(bool hasInput);
EventResult handleInsertMode(const Input &); EventResult handleInsertOrReplaceMode(const Input &);
EventResult handleReplaceMode(const Input &); void handleInsertMode(const Input &);
void handleReplaceMode(const Input &);
EventResult handleCommandMode(const Input &); EventResult handleCommandMode(const Input &);
@@ -1525,6 +1526,7 @@ public:
int lastPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos int lastPositionInLine(int line, bool onlyVisibleLines = true) const; // 1 based line, 0 based pos
int lineForPosition(int pos) 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 QString lineContents(int line) const; // 1 based line
QString textAt(int from, int to) const;
void setLineContents(int line, const QString &contents); // 1 based line void setLineContents(int line, const QString &contents); // 1 based line
int blockBoundary(const QString &left, const QString &right, int blockBoundary(const QString &left, const QString &right,
bool end, int count) const; // end or start position of current code block bool end, int count) const; // end or start position of current code block
@@ -1651,9 +1653,10 @@ public:
bool handleFfTt(QString key); bool handleFfTt(QString key);
void enterInsertMode();
void initVisualInsertMode(QChar command); void initVisualInsertMode(QChar command);
void enterReplaceMode(); void enterReplaceMode();
void enterInsertMode();
void enterInsertOrReplaceMode(Mode mode);
void enterCommandMode(Mode returnToMode = CommandMode); void enterCommandMode(Mode returnToMode = CommandMode);
void enterExMode(const QString &contents = QString()); void enterExMode(const QString &contents = QString());
void showMessage(MessageLevel level, const QString &msg); void showMessage(MessageLevel level, const QString &msg);
@@ -1675,7 +1678,7 @@ public:
void endEditBlock(); void endEditBlock();
void breakEditBlock() { m_breakEditBlock = true; } void breakEditBlock() { m_breakEditBlock = true; }
Q_SLOT void onContentsChanged(); Q_SLOT void onContentsChanged(int position, int charsRemoved, int charsAdded);
Q_SLOT void onUndoCommandAdded(); Q_SLOT void onUndoCommandAdded();
bool isInsertMode() const { return g.mode == InsertMode || g.mode == ReplaceMode; } bool isInsertMode() const { return g.mode == InsertMode || g.mode == ReplaceMode; }
@@ -1700,7 +1703,10 @@ public:
Q_SLOT void importSelection(); Q_SLOT void importSelection();
void exportSelection(); void exportSelection();
void recordInsertion(const QString &insert = QString()); void commitInsertState();
void invalidateInsertState();
bool isInsertStateValid() const;
void clearLastInsertion();
void ensureCursorVisible(); void ensureCursorVisible();
void insertInInsertMode(const QString &text); void insertInInsertMode(const QString &text);
@@ -1721,10 +1727,22 @@ public:
int m_oldInternalPosition; // copy from last event to check for external changes int m_oldInternalPosition; // copy from last event to check for external changes
int m_oldInternalAnchor; int m_oldInternalAnchor;
int m_oldPosition; // FIXME: Merge with above. int m_oldPosition; // FIXME: Merge with above.
int m_oldDocumentLength;
int m_register; int m_register;
bool m_visualBlockInsert; bool m_visualBlockInsert;
// Insert state to get last inserted text.
struct InsertState {
int pos1;
int pos2;
int backspaces;
int deletes;
QSet<int> spaces;
bool insertingSpaces;
QString textBeforeCursor;
bool newLineBefore;
bool newLineAfter;
} m_insertState;
bool m_fakeEnd; bool m_fakeEnd;
bool m_anchorPastEnd; bool m_anchorPastEnd;
bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol bool m_positionPastEnd; // '$' & 'l' in visual mode can move past eol
@@ -1781,10 +1799,8 @@ public:
void insertNewLine(); void insertNewLine();
bool handleInsertInEditor(const Input &input, QString *insert); bool handleInsertInEditor(const Input &input);
bool passEventToEditor(QEvent &event); // Pass event to editor widget without filtering. Returns true if event was processed. bool passEventToEditor(QEvent &event); // Pass event to editor widget without filtering. Returns true if event was processed.
// Guess insert command for text modification which happened externally (e.g. code-completion).
QString guessInsertCommand(int pos1, int pos2, int len1, int len2);
// undo handling // undo handling
int revision() const { return document()->revision(); } int revision() const { return document()->revision(); }
@@ -1799,7 +1815,7 @@ public:
int m_lastUndoSteps; int m_lastUndoSteps;
// extra data for '.' // extra data for '.'
void replay(const QString &text); void replay(const QString &text, int repeat = 1);
void setDotCommand(const QString &cmd) { g.dotCommand = cmd; } void setDotCommand(const QString &cmd) { g.dotCommand = cmd; }
void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); } void setDotCommand(const QString &cmd, int n) { g.dotCommand = cmd.arg(n); }
QString visualDotCommand() const; QString visualDotCommand() const;
@@ -1832,9 +1848,7 @@ public:
QString tabExpand(int len) const; QString tabExpand(int len) const;
Column indentation(const QString &line) const; Column indentation(const QString &line) const;
void insertAutomaticIndentation(bool goingDown, bool forceAutoIndent = false); void insertAutomaticIndentation(bool goingDown, bool forceAutoIndent = false);
bool removeAutomaticIndentation(); // true if something removed
// number of autoindented characters // number of autoindented characters
int m_justAutoIndented;
void handleStartOfLine(); void handleStartOfLine();
// register handling // register handling
@@ -1992,7 +2006,8 @@ FakeVimHandler::Private::Private(FakeVimHandler *parent, QWidget *widget)
m_textedit = qobject_cast<QTextEdit *>(widget); m_textedit = qobject_cast<QTextEdit *>(widget);
m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget); m_plaintextedit = qobject_cast<QPlainTextEdit *>(widget);
if (editor()) { if (editor()) {
connect(EDITOR(document()), SIGNAL(contentsChanged()), SLOT(onContentsChanged())); connect(EDITOR(document()), SIGNAL(contentsChange(int,int,int)),
SLOT(onContentsChanged(int,int,int)));
connect(EDITOR(document()), SIGNAL(undoCommandAdded()), SLOT(onUndoCommandAdded())); connect(EDITOR(document()), SIGNAL(undoCommandAdded()), SLOT(onUndoCommandAdded()));
m_lastUndoSteps = document()->availableUndoSteps(); m_lastUndoSteps = document()->availableUndoSteps();
} }
@@ -2012,14 +2027,13 @@ void FakeVimHandler::Private::init()
m_lastVisualModeInverted = false; m_lastVisualModeInverted = false;
m_targetColumn = 0; m_targetColumn = 0;
m_visualTargetColumn = 0; m_visualTargetColumn = 0;
m_justAutoIndented = 0;
m_ctrlVActive = false; m_ctrlVActive = false;
m_oldInternalAnchor = -1; m_oldInternalAnchor = -1;
m_oldInternalPosition = -1; m_oldInternalPosition = -1;
m_oldExternalAnchor = -1; m_oldExternalAnchor = -1;
m_oldExternalPosition = -1; m_oldExternalPosition = -1;
m_oldPosition = -1; m_oldPosition = -1;
m_oldDocumentLength = -1; m_insertState = InsertState();
m_breakEditBlock = false; m_breakEditBlock = false;
m_searchStartPosition = 0; m_searchStartPosition = 0;
m_searchFromScreenLine = 0; m_searchFromScreenLine = 0;
@@ -2032,8 +2046,10 @@ void FakeVimHandler::Private::init()
void FakeVimHandler::Private::focus() void FakeVimHandler::Private::focus()
{ {
stopIncrementalFind(); stopIncrementalFind();
if (!isInsertMode()) {
leaveVisualMode(); leaveVisualMode();
resetCommandMode(); resetCommandMode();
}
updateCursorShape(); updateCursorShape();
if (!g.inFakeVim || g.mode != CommandMode) if (!g.inFakeVim || g.mode != CommandMode)
updateMiniBuffer(); updateMiniBuffer();
@@ -2298,31 +2314,62 @@ void FakeVimHandler::Private::exportSelection()
m_oldExternalAnchor = anchor(); m_oldExternalAnchor = anchor();
} }
void FakeVimHandler::Private::recordInsertion(const QString &insert) void FakeVimHandler::Private::commitInsertState()
{ {
const int pos = position(); if (!isInsertStateValid())
return;
if (insert.isNull()) { // Get raw inserted text.
const int dist = pos - m_oldPosition; m_lastInsertion = textAt(m_insertState.pos1, m_insertState.pos2);
if (dist > 0) { // Escape special characters and spaces inserted by user (not by auto-indentation).
Range range(m_oldPosition, pos); for (int i = m_lastInsertion.size() - 1; i >= 0; --i) {
QString text = selectText(range); const int pos = m_insertState.pos1 + i;
// escape text like <ESC> const ushort c = document()->characterAt(pos).unicode();
text.replace(_("<"), _("<LT>")); if (c == '<')
m_lastInsertion.append(text); m_lastInsertion.replace(i, 1, _("<LT>"));
} else if (dist < 0) { else if ((c == ' ' || c == '\t') && m_insertState.spaces.contains(pos))
m_lastInsertion.resize(m_lastInsertion.size() + dist); m_lastInsertion.replace(i, 1, _(c == ' ' ? "<SPACE>" : "<TAB>"));
}
} else {
m_lastInsertion += insert;
} }
if (m_oldPosition != pos) { // Remove unnecessary backspaces.
m_oldPosition = pos; while (m_insertState.backspaces > 0 && !m_lastInsertion.isEmpty() && m_lastInsertion[0].isSpace())
setTargetColumn(); --m_insertState.backspaces;
// backspaces in front of inserted text
m_lastInsertion.prepend(QString(_("<BS>")).repeated(m_insertState.backspaces));
// deletes after inserted text
m_lastInsertion.prepend(QString(_("<DELETE>")).repeated(m_insertState.deletes));
// Remove indentation.
m_lastInsertion.replace(QRegExp(_("(^|\n)[\\t ]+")), _("\\1"));
} }
m_oldDocumentLength = document()->characterCount();
void FakeVimHandler::Private::invalidateInsertState()
{
m_oldPosition = position();
m_insertState.pos1 = -1;
m_insertState.pos2 = m_oldPosition;
m_insertState.backspaces = 0;
m_insertState.deletes = 0;
m_insertState.spaces.clear();
m_insertState.insertingSpaces = false;
m_insertState.textBeforeCursor = textAt(document()->findBlock(m_oldPosition).position(),
m_oldPosition);
m_insertState.newLineBefore = false;
m_insertState.newLineAfter = false;
}
bool FakeVimHandler::Private::isInsertStateValid() const
{
return m_insertState.pos1 != -1;
}
void FakeVimHandler::Private::clearLastInsertion()
{
invalidateInsertState();
m_lastInsertion.clear();
m_insertState.pos1 = m_insertState.pos2;
} }
void FakeVimHandler::Private::ensureCursorVisible() void FakeVimHandler::Private::ensureCursorVisible()
@@ -2469,10 +2516,8 @@ EventResult FakeVimHandler::Private::handleDefaultKey(const Input &input)
return handleSearchSubSubMode(input); return handleSearchSubSubMode(input);
else if (g.mode == CommandMode) else if (g.mode == CommandMode)
return handleCommandMode(input); return handleCommandMode(input);
else if (g.mode == InsertMode) else if (g.mode == InsertMode || g.mode == ReplaceMode)
return handleInsertMode(input); return handleInsertOrReplaceMode(input);
else if (g.mode == ReplaceMode)
return handleReplaceMode(input);
else if (g.mode == ExMode) else if (g.mode == ExMode)
return handleExMode(input); return handleExMode(input);
return EventUnhandled; return EventUnhandled;
@@ -2965,8 +3010,6 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
insertAutomaticIndentation(true); insertAutomaticIndentation(true);
endEditBlock(); endEditBlock();
setTargetColumn(); setTargetColumn();
m_lastInsertion.clear();
g.returnToMode = InsertMode;
} else if (g.submode == DeleteSubMode) { } else if (g.submode == DeleteSubMode) {
pushUndoState(false); pushUndoState(false);
beginEditBlock(); beginEditBlock();
@@ -3037,8 +3080,14 @@ void FakeVimHandler::Private::finishMovement(const QString &dotCommandMovement)
if (!dotCommand.isEmpty() && !dotCommandMovement.isEmpty()) if (!dotCommand.isEmpty() && !dotCommandMovement.isEmpty())
setDotCommand(dotCommand + dotCommandMovement); setDotCommand(dotCommand + dotCommandMovement);
// Change command continues in insert mode.
if (g.submode == ChangeSubMode) {
clearCommandMode();
enterInsertMode();
} else {
resetCommandMode(); resetCommandMode();
} }
}
void FakeVimHandler::Private::resetCommandMode() void FakeVimHandler::Private::resetCommandMode()
{ {
@@ -3052,8 +3101,8 @@ void FakeVimHandler::Private::resetCommandMode()
else else
enterReplaceMode(); enterReplaceMode();
moveToTargetColumn(); moveToTargetColumn();
invalidateInsertState();
m_lastInsertion = lastInsertion; m_lastInsertion = lastInsertion;
m_oldPosition = position();
} }
if (isNoVisualMode()) if (isNoVisualMode())
setAnchor(); setAnchor();
@@ -3901,19 +3950,20 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
} }
const int line = lineNumber(block()); const int line = lineNumber(block());
enterInsertMode();
beginEditBlock(); beginEditBlock();
enterInsertMode();
setPosition(appendLine ? lastPositionInLine(line) : firstPositionInLine(line));
clearLastInsertion();
setAnchor();
insertNewLine();
if (appendLine) { if (appendLine) {
setPosition(lastPositionInLine(line)); m_insertState.newLineBefore = true;
setAnchor();
insertNewLine();
} else { } else {
setPosition(firstPositionInLine(line));
setAnchor();
insertNewLine();
moveUp(); moveUp();
m_oldPosition = position();
m_insertState.pos1 = m_oldPosition;
m_insertState.newLineAfter = true;
} }
recordInsertion(QString::fromLatin1("\n"));
setTargetColumn(); setTargetColumn();
endEditBlock(); endEditBlock();
@@ -4365,44 +4415,55 @@ bool FakeVimHandler::Private::handleMacroExecuteSubMode(const Input &input)
return result; return result;
} }
EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) EventResult FakeVimHandler::Private::handleInsertOrReplaceMode(const Input &input)
{ {
bool clearLastInsertion = m_breakEditBlock; if (position() < m_insertState.pos1 || position() > m_insertState.pos2) {
if (m_oldPosition != position()) { commitInsertState();
if (clearLastInsertion) { invalidateInsertState();
clearLastInsertion = false;
m_lastInsertion = _("<INSERT>");
}
recordInsertion();
} }
if (g.mode == InsertMode)
handleInsertMode(input);
else
handleReplaceMode(input);
if (!isInsertMode() || m_breakEditBlock
|| position() < m_insertState.pos1 || position() > m_insertState.pos2) {
commitInsertState();
invalidateInsertState();
breakEditBlock();
} else if (m_oldPosition == position()) {
setTargetColumn();
}
updateMiniBuffer();
// We don't want fancy stuff in insert mode.
return EventHandled;
}
void FakeVimHandler::Private::handleReplaceMode(const Input &input)
{
if (input.isEscape()) { if (input.isEscape()) {
commitInsertState();
moveLeft(qMin(1, leftDist())); moveLeft(qMin(1, leftDist()));
enterCommandMode(); enterCommandMode();
g.dotCommand += m_lastInsertion; g.dotCommand.append(m_lastInsertion + _("<ESC>"));
g.dotCommand += QChar(27);
} else if (input.isKey(Key_Left)) { } else if (input.isKey(Key_Left)) {
breakEditBlock(); moveLeft();
moveLeft(1);
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_Right)) { } else if (input.isKey(Key_Right)) {
breakEditBlock(); moveRight();
moveRight(1);
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_Up)) { } else if (input.isKey(Key_Up)) {
breakEditBlock(); moveUp();
moveUp(1);
} else if (input.isKey(Key_Down)) { } else if (input.isKey(Key_Down)) {
breakEditBlock(); moveDown();
moveDown(1);
} else if (input.isKey(Key_Insert)) { } else if (input.isKey(Key_Insert)) {
g.mode = InsertMode; g.mode = InsertMode;
recordInsertion(_("<INSERT>"));
} else if (input.isControl('o')) { } else if (input.isControl('o')) {
enterCommandMode(ReplaceMode); enterCommandMode(ReplaceMode);
} else { } else {
if (clearLastInsertion)
m_lastInsertion = _("<INSERT>");
joinPreviousEditBlock(); joinPreviousEditBlock();
if (!atEndOfLine()) { if (!atEndOfLine()) {
setAnchor(); setAnchor();
@@ -4413,38 +4474,34 @@ EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input)
setAnchor(); setAnchor();
insertText(text); insertText(text);
endEditBlock(); endEditBlock();
recordInsertion();
} }
m_oldPosition = position();
updateMiniBuffer();
return EventHandled;
} }
EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) void FakeVimHandler::Private::handleInsertMode(const Input &input)
{ {
bool clearLastInsertion = m_breakEditBlock && !m_lastInsertion.isEmpty();
int pos2 = position();
int len2 = document()->characterCount();
if (m_oldPosition != pos2 || m_oldDocumentLength != len2) {
if (clearLastInsertion) {
clearLastInsertion = false;
m_lastInsertion.clear();
g.dotCommand = _("i");
}
recordInsertion(guessInsertCommand(m_oldPosition, pos2, m_oldDocumentLength, len2));
}
QString insert;
if (input.isEscape()) { if (input.isEscape()) {
bool newLineAfter = m_insertState.newLineAfter;
bool newLineBefore = m_insertState.newLineBefore;
// Repeat insertion [count] times. // Repeat insertion [count] times.
// One instance was already physically inserted while typing. // One instance was already physically inserted while typing.
if (!m_breakEditBlock && !m_lastInsertion.isEmpty()) { if (!m_breakEditBlock && isInsertStateValid()) {
const QString text = m_lastInsertion; commitInsertState();
const int repeat = count();
QString text = m_lastInsertion;
const QString dotCommand = g.dotCommand;
const int repeat = count() - 1;
m_lastInsertion.clear(); m_lastInsertion.clear();
joinPreviousEditBlock(); joinPreviousEditBlock();
replay(text.repeated(repeat - 1));
if (newLineAfter) {
text.chop(1);
text.prepend(_("<END>\n"));
} else if (newLineBefore) {
text.prepend(_("<END>"));
}
replay(text, repeat);
if (m_visualBlockInsert && !text.contains(QLatin1Char('\n'))) { if (m_visualBlockInsert && !text.contains(QLatin1Char('\n'))) {
const CursorPosition lastAnchor = mark(QLatin1Char('<')).position; const CursorPosition lastAnchor = mark(QLatin1Char('<')).position;
@@ -4452,7 +4509,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
CursorPosition startPos(lastAnchor.line, CursorPosition startPos(lastAnchor.line,
qMin(lastPosition.column, lastAnchor.column)); qMin(lastPosition.column, lastAnchor.column));
CursorPosition pos = startPos; CursorPosition pos = startPos;
if (g.dotCommand.endsWith(QLatin1Char('A'))) if (dotCommand.endsWith(QLatin1Char('A')))
pos.column = qMax(lastPosition.column, lastAnchor.column) + 1; pos.column = qMax(lastPosition.column, lastAnchor.column) + 1;
while (pos.line < lastPosition.line) { while (pos.line < lastPosition.line) {
++pos.line; ++pos.line;
@@ -4462,7 +4519,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
break; break;
m_cursor = tc; m_cursor = tc;
if (tc.positionInBlock() == pos.column) if (tc.positionInBlock() == pos.column)
replay(text.repeated(repeat)); replay(text, repeat + 1);
} }
setCursorPosition(startPos); setCursorPosition(startPos);
@@ -4474,14 +4531,14 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
breakEditBlock(); breakEditBlock();
m_lastInsertion = text; m_lastInsertion = text;
g.dotCommand = dotCommand;
} else { } else {
moveLeft(qMin(1, leftDist())); moveLeft(qMin(1, leftDist()));
} }
// If command is 'o' or 'O' don't include the first line feed in dot command. if (newLineBefore || newLineAfter)
if (g.dotCommand.endsWith(QLatin1Char('o'), Qt::CaseInsensitive)) m_lastInsertion.remove(0, m_lastInsertion.indexOf(QLatin1Char('\n')) + 1);
m_lastInsertion.remove(0, 1); g.dotCommand.append(m_lastInsertion + _("<ESC>"));
g.dotCommand += m_lastInsertion + _("<ESC>");
enterCommandMode(); enterCommandMode();
setTargetColumn(); setTargetColumn();
@@ -4493,7 +4550,6 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
enterCommandMode(InsertMode); enterCommandMode(InsertMode);
} else if (input.isControl('v')) { } else if (input.isControl('v')) {
m_ctrlVActive = true; m_ctrlVActive = true;
insert = _("<C-V>");
} else if (input.isControl('w')) { } else if (input.isControl('w')) {
const int blockNumber = m_cursor.blockNumber(); const int blockNumber = m_cursor.blockNumber();
const int endPos = position(); const int endPos = position();
@@ -4503,10 +4559,8 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
const int beginPos = position(); const int beginPos = position();
Range range(beginPos, endPos, RangeCharMode); Range range(beginPos, endPos, RangeCharMode);
removeText(range); removeText(range);
insert = _("<C-W>");
} else if (input.isKey(Key_Insert)) { } else if (input.isKey(Key_Insert)) {
g.mode = ReplaceMode; g.mode = ReplaceMode;
insert = _("<INSERT>");
} else if (input.isKey(Key_Left)) { } else if (input.isKey(Key_Left)) {
moveLeft(); moveLeft();
setTargetColumn(); setTargetColumn();
@@ -4514,11 +4568,9 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
moveToNextWordStart(1, false, false); moveToNextWordStart(1, false, false);
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_Down)) { } else if (input.isKey(Key_Down)) {
//removeAutomaticIndentation();
g.submode = NoSubMode; g.submode = NoSubMode;
moveDown(); moveDown();
} else if (input.isKey(Key_Up)) { } else if (input.isKey(Key_Up)) {
//removeAutomaticIndentation();
g.submode = NoSubMode; g.submode = NoSubMode;
moveUp(); moveUp();
} else if (input.isKey(Key_Right)) { } else if (input.isKey(Key_Right)) {
@@ -4536,17 +4588,15 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
setTargetColumn(); setTargetColumn();
m_targetColumn = -1; m_targetColumn = -1;
} else if (input.isReturn() || input.isControl('j') || input.isControl('m')) { } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) {
if (!input.isReturn() || !handleInsertInEditor(input, &insert)) { if (!input.isReturn() || !handleInsertInEditor(input)) {
joinPreviousEditBlock(); joinPreviousEditBlock();
g.submode = NoSubMode; g.submode = NoSubMode;
insertNewLine(); insertNewLine();
insert = _("\n");
endEditBlock(); endEditBlock();
} }
} else if (input.isBackspace()) { } else if (input.isBackspace()) {
if (!handleInsertInEditor(input, &insert)) { if (!handleInsertInEditor(input)) {
joinPreviousEditBlock(); joinPreviousEditBlock();
m_justAutoIndented = 0;
if (!m_lastInsertion.isEmpty() if (!m_lastInsertion.isEmpty()
|| hasConfig(ConfigBackspace, "start") || hasConfig(ConfigBackspace, "start")
|| hasConfig(ConfigBackspace, "2")) { || hasConfig(ConfigBackspace, "2")) {
@@ -4567,34 +4617,29 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
m_cursor.deletePreviousChar(); m_cursor.deletePreviousChar();
} }
} }
insert = _("<BS>");
endEditBlock(); endEditBlock();
} }
} else if (input.isKey(Key_Delete)) { } else if (input.isKey(Key_Delete)) {
if (!handleInsertInEditor(input, &insert)) { if (!handleInsertInEditor(input)) {
joinPreviousEditBlock(); joinPreviousEditBlock();
m_cursor.deleteChar(); m_cursor.deleteChar();
insert = _("<DELETE>");
endEditBlock(); endEditBlock();
} }
} else if (input.isKey(Key_PageDown) || input.isControl('f')) { } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
removeAutomaticIndentation();
movePageDown(); movePageDown();
} else if (input.isKey(Key_PageUp) || input.isControl('b')) { } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
removeAutomaticIndentation();
movePageUp(); movePageUp();
} else if (input.isKey(Key_Tab)) { } else if (input.isKey(Key_Tab)) {
m_justAutoIndented = 0; m_insertState.insertingSpaces = true;
if (hasConfig(ConfigExpandTab)) { if (hasConfig(ConfigExpandTab)) {
const int ts = config(ConfigTabStop).toInt(); const int ts = config(ConfigTabStop).toInt();
const int col = logicalCursorColumn(); const int col = logicalCursorColumn();
QString str = QString(ts - col % ts, QLatin1Char(' ')); QString str = QString(ts - col % ts, QLatin1Char(' '));
m_lastInsertion.append(str);
insertText(str); insertText(str);
} else { } else {
insertInInsertMode(input.raw()); insertInInsertMode(input.raw());
} }
insert = _("\t"); m_insertState.insertingSpaces = false;
} else if (input.isControl('d')) { } else if (input.isControl('d')) {
// remove one level of indentation from the current line // remove one level of indentation from the current line
int shift = config(ConfigShiftWidth).toInt(); int shift = config(ConfigShiftWidth).toInt();
@@ -4613,60 +4658,33 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
break; break;
} }
removeText(Range(pos, pos+i)); removeText(Range(pos, pos+i));
insert = _("<C-D>");
//} else if (key >= control('a') && key <= control('z')) {
// // ignore these
} else if (input.isControl('p') || input.isControl('n')) { } else if (input.isControl('p') || input.isControl('n')) {
QTextCursor tc = m_cursor; QTextCursor tc = m_cursor;
moveToNextWordStart(1, false, false); moveToNextWordStart(1, false, false);
QString str = selectText(Range(position(), tc.position())); QString str = selectText(Range(position(), tc.position()));
m_cursor = tc; m_cursor = tc;
emit q->simpleCompletionRequested(str, input.isControl('n')); emit q->simpleCompletionRequested(str, input.isControl('n'));
if (input.isControl('p'))
insert = _("<C-P>");
else
insert = _("<C-N>");
} else if (input.isShift(Qt::Key_Insert)) { } else if (input.isShift(Qt::Key_Insert)) {
// Insert text from clipboard. // Insert text from clipboard.
QClipboard *clipboard = QApplication::clipboard(); QClipboard *clipboard = QApplication::clipboard();
const QMimeData *data = clipboard->mimeData(); const QMimeData *data = clipboard->mimeData();
if (data && data->hasText()) if (data && data->hasText())
insertInInsertMode(data->text()); insertInInsertMode(data->text());
insert = _("<S-INSERT>");
} else { } else {
if (!handleInsertInEditor(input, &insert)) { m_insertState.insertingSpaces = input.isKey(Key_Space);
insert = input.text(); if (!handleInsertInEditor(input)) {
if (!insert.isEmpty()) { const QString toInsert = input.text();
insertInInsertMode(insert); if (toInsert.isEmpty())
insert.replace(_("<"), _("<LT>")); return;
} else { insertInInsertMode(toInsert);
// We don't want fancy stuff in insert mode.
return EventHandled;
} }
m_insertState.insertingSpaces = false;
} }
} }
if (insert.isNull()) {
breakEditBlock();
m_oldPosition = position();
m_visualBlockInsert = false;
resetCount();
} else {
if (clearLastInsertion) {
m_lastInsertion.clear();
g.dotCommand = _("i");
}
recordInsertion(insert);
}
updateMiniBuffer();
return EventHandled;
}
void FakeVimHandler::Private::insertInInsertMode(const QString &text) void FakeVimHandler::Private::insertInInsertMode(const QString &text)
{ {
joinPreviousEditBlock(); joinPreviousEditBlock();
m_justAutoIndented = 0;
insertText(text); insertText(text);
if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) { if (hasConfig(ConfigSmartIndent) && isElectricCharacter(text.at(0))) {
const QString leftText = block().text() const QString leftText = block().text()
@@ -5947,7 +5965,11 @@ void FakeVimHandler::Private::indentText(const Range &range, QChar typedChar)
int endBlock = document()->findBlock(range.endPos).blockNumber(); int endBlock = document()->findBlock(range.endPos).blockNumber();
if (beginBlock > endBlock) if (beginBlock > endBlock)
qSwap(beginBlock, endBlock); qSwap(beginBlock, endBlock);
// Don't remember current indentation in last text insertion.
const QString lastInsertion = m_lastInsertion;
emit q->indentRegion(beginBlock, endBlock, typedChar); emit q->indentRegion(beginBlock, endBlock, typedChar);
m_lastInsertion = lastInsertion;
} }
bool FakeVimHandler::Private::isElectricCharacter(QChar c) const bool FakeVimHandler::Private::isElectricCharacter(QChar c) const
@@ -6841,7 +6863,7 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace)
void FakeVimHandler::Private::insertNewLine() void FakeVimHandler::Private::insertNewLine()
{ {
if ( hasConfig(ConfigPassKeys) ) { if ( m_editBlockLevel <= 1 && hasConfig(ConfigPassKeys) ) {
QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, QLatin1String("\n")); QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, QLatin1String("\n"));
if (passEventToEditor(event)) if (passEventToEditor(event))
return; return;
@@ -6851,27 +6873,19 @@ void FakeVimHandler::Private::insertNewLine()
insertAutomaticIndentation(true); insertAutomaticIndentation(true);
} }
bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *insert) bool FakeVimHandler::Private::handleInsertInEditor(const Input &input)
{ {
if (m_editBlockLevel > 0 || !hasConfig(ConfigPassKeys)) if (m_editBlockLevel > 0 || !hasConfig(ConfigPassKeys))
return false; return false;
joinPreviousEditBlock(); joinPreviousEditBlock();
const int pos1 = position();
const int len1 = lastPositionInDocument();
QKeyEvent event(QEvent::KeyPress, input.key(), QKeyEvent event(QEvent::KeyPress, input.key(),
static_cast<Qt::KeyboardModifiers>(input.modifiers()), input.text()); static_cast<Qt::KeyboardModifiers>(input.modifiers()), input.text());
setAnchor(); setAnchor();
if (!passEventToEditor(event)) if (!passEventToEditor(event))
return false; return false;
const int pos2 = position();
const int len2 = lastPositionInDocument();
*insert = guessInsertCommand(pos1, pos2, len1, len2);
endEditBlock(); endEditBlock();
return true; return true;
@@ -6898,75 +6912,19 @@ bool FakeVimHandler::Private::passEventToEditor(QEvent &event)
return accepted; return accepted;
} }
QString FakeVimHandler::Private::guessInsertCommand(int pos1, int pos2, int len1, int len2)
{
QString insert;
// Guess the inserted/deleted text.
if (len1 > len2) {
// Text deleted.
if (pos1 == pos2) {
// Text after cursor deleted.
insert = QString(_("<C-O>%1x")).arg(len1 - len2);
} else if (pos1 > pos2) {
// Text in front of cursor deleted.
const int backspaces = pos1 - pos2;
insert = QString(_("<BS>")).repeated(backspaces);
// Some text after cursor may have beed deleted too.
const int deletes = len1 - len2 - backspaces;
if (deletes > 0)
insert.append(QString(_("<C-O>%1x")).arg(deletes));
}
} else if (len1 < len2) {
// Text inserted.
if (pos1 < pos2) {
QTextCursor tc = m_cursor;
tc.setPosition(pos1);
tc.setPosition(pos2, KeepAnchor);
insert = QString(tc.selectedText()).replace(_("<"), _("<LT>"));
const int textLen = pos2 - pos1;
const int rest = len2 - len1 - textLen;
if (rest > 0) {
// Text inserted after new cursor position.
// On dot command, cursor must insert the same text and move in front of it.
tc.setPosition(pos2);
tc.setPosition(pos2 + rest, KeepAnchor);
insert.append(QString(tc.selectedText()).replace(_("<"), _("<LT>")));
const int up = document()->findBlock(pos2).blockNumber()
- document()->findBlock(pos1).blockNumber();
if (up > 0) {
insert.append(QString(_("<UP>")).repeated(up));
insert.append(_("<END>"));
const int right = rightDist();
if (right > 0)
insert.append(QString(_("<LEFT>")).repeated(right));
} else {
insert.append(QString(_("<LEFT>")).repeated(rest));
}
}
}
} else {
// Document length is unchanged so assume that no text inserted or deleted.
// Check if cursor moved.
const int right = pos2 - pos1;
if (right > 0)
insert = QString(_("<RIGHT>")).repeated(right);
else if (right < 0)
insert = QString(_("<LEFT>")).repeated(-right);
else
insert = _(""); // Empty non-null string.
}
return insert;
}
QString FakeVimHandler::Private::lineContents(int line) const QString FakeVimHandler::Private::lineContents(int line) const
{ {
return document()->findBlockByLineNumber(line - 1).text(); return document()->findBlockByLineNumber(line - 1).text();
} }
QString FakeVimHandler::Private::textAt(int from, int to) const
{
QTextCursor tc(document());
tc.setPosition(from);
tc.setPosition(to, KeepAnchor);
return tc.selectedText().replace(ParagraphSeparator, QLatin1Char('\n'));
}
void FakeVimHandler::Private::setLineContents(int line, const QString &contents) void FakeVimHandler::Private::setLineContents(int line, const QString &contents)
{ {
QTextBlock block = document()->findBlockByLineNumber(line - 1); QTextBlock block = document()->findBlockByLineNumber(line - 1);
@@ -7155,7 +7113,7 @@ QWidget *FakeVimHandler::Private::editor() const
void FakeVimHandler::Private::joinPreviousEditBlock() void FakeVimHandler::Private::joinPreviousEditBlock()
{ {
UNDO_DEBUG("JOIN"); UNDO_DEBUG("JOIN");
if (m_breakEditBlock && m_editBlockLevel == 0) { if (m_breakEditBlock) {
beginEditBlock(); beginEditBlock();
QTextCursor tc(m_cursor); QTextCursor tc(m_cursor);
tc.setPosition(tc.position()); tc.setPosition(tc.position());
@@ -7163,6 +7121,7 @@ void FakeVimHandler::Private::joinPreviousEditBlock()
tc.insertText(_("X")); tc.insertText(_("X"));
tc.deletePreviousChar(); tc.deletePreviousChar();
tc.endEditBlock(); tc.endEditBlock();
m_breakEditBlock = false;
} else { } else {
if (m_editBlockLevel == 0 && !m_undo.empty()) if (m_editBlockLevel == 0 && !m_undo.empty())
m_undoState = m_undo.pop(); m_undoState = m_undo.pop();
@@ -7175,8 +7134,9 @@ void FakeVimHandler::Private::beginEditBlock(bool largeEditBlock)
UNDO_DEBUG("BEGIN EDIT BLOCK"); UNDO_DEBUG("BEGIN EDIT BLOCK");
if (!largeEditBlock && !m_undoState.isValid()) if (!largeEditBlock && !m_undoState.isValid())
pushUndoState(false); pushUndoState(false);
if (m_editBlockLevel == 0)
m_breakEditBlock = true;
++m_editBlockLevel; ++m_editBlockLevel;
m_breakEditBlock = false;
} }
void FakeVimHandler::Private::endEditBlock() void FakeVimHandler::Private::endEditBlock()
@@ -7189,10 +7149,54 @@ void FakeVimHandler::Private::endEditBlock()
m_undo.push(m_undoState); m_undo.push(m_undoState);
m_undoState = State(); m_undoState = State();
} }
if (m_editBlockLevel == 0)
m_breakEditBlock = false;
} }
void FakeVimHandler::Private::onContentsChanged() void FakeVimHandler::Private::onContentsChanged(int position, int charsRemoved, int charsAdded)
{ {
// Record inserted and deleted text in insert mode.
if (isInsertMode() && (charsAdded > 0 || charsRemoved > 0)) {
if (!isInsertStateValid()) {
m_insertState.pos1 = m_oldPosition;
g.dotCommand = _("i");
resetCount();
}
// Ignore changes outside inserted text (e.g. renaming other occurrences of a variable).
if (position + charsRemoved >= m_insertState.pos1 && position <= m_insertState.pos2) {
if (charsRemoved > 0) {
if (position < m_insertState.pos1) {
// backspaces
const int bs = m_insertState.pos1 - position;
const QString inserted = textAt(position, position + charsAdded);
const QString removed = m_insertState.textBeforeCursor.right(bs);
// Ignore backspaces if same text was just inserted.
if ( !inserted.startsWith(removed) ) {
m_insertState.backspaces += bs;
m_insertState.pos1 = position;
m_insertState.pos2 = qMax(position, m_insertState.pos2 - bs);
}
} else if (position + charsRemoved > m_insertState.pos2) {
// deletes
m_insertState.deletes += position + charsRemoved - m_insertState.pos2;
}
} else if (charsAdded > 0 && m_insertState.insertingSpaces) {
for (int i = position; i < position + charsAdded; ++i) {
const QChar c = document()->characterAt(i);
if (c.unicode() == ' ' || c.unicode() == '\t')
m_insertState.spaces.insert(i);
}
}
m_insertState.pos2 = qMax(m_insertState.pos2 + charsAdded - charsRemoved,
position + charsAdded);
m_oldPosition = position + charsAdded;
m_insertState.textBeforeCursor = textAt(document()->findBlock(m_oldPosition).position(),
m_oldPosition);
}
}
if (!document()->isUndoAvailable()) if (!document()->isUndoAvailable())
m_undo.clear(); m_undo.clear();
@@ -7321,28 +7325,31 @@ void FakeVimHandler::Private::updateCursorShape()
void FakeVimHandler::Private::enterReplaceMode() void FakeVimHandler::Private::enterReplaceMode()
{ {
g.mode = ReplaceMode; enterInsertOrReplaceMode(ReplaceMode);
g.submode = NoSubMode;
g.subsubmode = NoSubSubMode;
m_lastInsertion.clear();
m_oldPosition = position();
g.returnToMode = ReplaceMode;
} }
void FakeVimHandler::Private::enterInsertMode() void FakeVimHandler::Private::enterInsertMode()
{ {
g.mode = InsertMode; enterInsertOrReplaceMode(InsertMode);
g.submode = NoSubMode; }
g.subsubmode = NoSubSubMode;
m_lastInsertion.clear(); void FakeVimHandler::Private::enterInsertOrReplaceMode(Mode mode)
m_oldPosition = position(); {
m_oldDocumentLength = document()->characterCount(); QTC_ASSERT(mode == InsertMode || mode == ReplaceMode, return);
if (g.returnToMode != InsertMode) { if (g.mode == mode)
g.returnToMode = InsertMode; return;
if (mode == InsertMode && g.returnToMode != InsertMode) {
// If entering insert mode from command mode, m_targetColumn shouldn't be -1 (end of line). // If entering insert mode from command mode, m_targetColumn shouldn't be -1 (end of line).
if (m_targetColumn == -1) if (m_targetColumn == -1)
setTargetColumn(); setTargetColumn();
} }
g.mode = mode;
g.submode = NoSubMode;
g.subsubmode = NoSubSubMode;
g.returnToMode = mode;
clearLastInsertion();
} }
void FakeVimHandler::Private::initVisualInsertMode(QChar command) void FakeVimHandler::Private::initVisualInsertMode(QChar command)
@@ -7473,9 +7480,7 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool fo
if (hasConfig(ConfigSmartIndent)) { if (hasConfig(ConfigSmartIndent)) {
QTextBlock bl = block(); QTextBlock bl = block();
Range range(bl.position(), bl.position()); Range range(bl.position(), bl.position());
const int oldSize = bl.text().size();
indentText(range, QLatin1Char('\n')); indentText(range, QLatin1Char('\n'));
m_justAutoIndented = bl.text().size() - oldSize;
} else { } else {
QTextBlock bl = goingDown ? block().previous() : block().next(); QTextBlock bl = goingDown ? block().previous() : block().next();
QString text = bl.text(); QString text = bl.text();
@@ -7486,37 +7491,28 @@ void FakeVimHandler::Private::insertAutomaticIndentation(bool goingDown, bool fo
text.truncate(pos); text.truncate(pos);
// FIXME: handle 'smartindent' and 'cindent' // FIXME: handle 'smartindent' and 'cindent'
insertText(text); insertText(text);
m_justAutoIndented = text.size();
} }
} }
bool FakeVimHandler::Private::removeAutomaticIndentation()
{
if (!hasConfig(ConfigAutoIndent) || m_justAutoIndented == 0)
return false;
/*
m_tc.movePosition(StartOfLine, KeepAnchor);
m_tc.removeSelectedText();
m_lastInsertion.chop(m_justAutoIndented);
*/
m_justAutoIndented = 0;
return true;
}
void FakeVimHandler::Private::handleStartOfLine() void FakeVimHandler::Private::handleStartOfLine()
{ {
if (hasConfig(ConfigStartOfLine)) if (hasConfig(ConfigStartOfLine))
moveToFirstNonBlankOnLine(); moveToFirstNonBlankOnLine();
} }
void FakeVimHandler::Private::replay(const QString &command) void FakeVimHandler::Private::replay(const QString &command, int repeat)
{ {
if (repeat <= 0)
return;
//qDebug() << "REPLAY: " << quoteUnprintable(command); //qDebug() << "REPLAY: " << quoteUnprintable(command);
clearCommandMode(); clearCommandMode();
Inputs inputs(command); Inputs inputs(command);
for (int i = 0; i < repeat; ++i) {
foreach (const Input &in, inputs) { foreach (const Input &in, inputs) {
if (handleDefaultKey(in) != EventHandled) if (handleDefaultKey(in) != EventHandled)
break; return;
}
} }
} }