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