diff --git a/src/plugins/fakevim/fakevim_test.cpp b/src/plugins/fakevim/fakevim_test.cpp index 5a663e1132dc2938ca86252b2002deb3f2f1c7e7..24bd9c79bcca4573ab781a066a3c2ee212311c14 100644 --- a/src/plugins/fakevim/fakevim_test.cpp +++ b/src/plugins/fakevim/fakevim_test.cpp @@ -274,6 +274,7 @@ void FakeVimPlugin::setup(TestData *data) { setupTest(&data->title, &data->handler, &data->edit); data->reset(); + data->doCommand("set nopasskeys | set nopasscontrolkey"); } diff --git a/src/plugins/fakevim/fakevimactions.cpp b/src/plugins/fakevim/fakevimactions.cpp index 989f916ab6d2bc3530215e826fd2bf263da2cdb9..22aad3fc26d860bed3dec39ca1e74b7c20fc470c 100644 --- a/src/plugins/fakevim/fakevimactions.cpp +++ b/src/plugins/fakevim/fakevimactions.cpp @@ -180,7 +180,7 @@ FakeVimSettings *theFakeVimSettings() #endif createAction(s, ConfigShowMarks, false, _("ShowMarks"), _("sm")); createAction(s, ConfigPassControlKey, false, _("PassControlKey"), _("pck")); - createAction(s, ConfigPassNewLine, false, _("PassNewLine"), _("pnl")); + createAction(s, ConfigPassKeys, true, _("PassKeys"), _("pk")); // Emulated Vim setting createAction(s, ConfigStartOfLine, true, _("StartOfLine"), _("sol")); diff --git a/src/plugins/fakevim/fakevimactions.h b/src/plugins/fakevim/fakevimactions.h index ce20850c52d29c2564f7044ef3574423671c94fa..c0b37867f9866871c6df2022bbb8ba30bb6bbc3b 100644 --- a/src/plugins/fakevim/fakevimactions.h +++ b/src/plugins/fakevim/fakevimactions.h @@ -93,7 +93,7 @@ enum FakeVimSettingsCode // other actions ConfigShowMarks, ConfigPassControlKey, - ConfigPassNewLine, + ConfigPassKeys, ConfigClipboard, ConfigShowCmd, ConfigScrollOff diff --git a/src/plugins/fakevim/fakevimhandler.cpp b/src/plugins/fakevim/fakevimhandler.cpp index 91a35db9056add73daa39d5dfe28891e4df4b127..3dd5695efcbb45abb6aa4b15b72255b6579aa16d 100644 --- a/src/plugins/fakevim/fakevimhandler.cpp +++ b/src/plugins/fakevim/fakevimhandler.cpp @@ -949,6 +949,8 @@ public: int key() const { return m_key; } + int modifiers() const { return m_modifiers; } + // Return raw character for macro recording or dot command. QChar raw() const { @@ -1708,6 +1710,7 @@ 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; QString m_mvcount; QString m_opcount; @@ -1772,6 +1775,11 @@ public: void insertNewLine(); + bool handleInsertInEditor(const Input &input, QString *insert); + 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()->availableUndoSteps(); } void undoRedo(bool undo); @@ -1974,6 +1982,7 @@ void FakeVimHandler::Private::init() m_oldExternalAnchor = -1; m_oldExternalPosition = -1; m_oldPosition = -1; + m_oldDocumentLength = -1; m_breakEditBlock = false; m_searchStartPosition = 0; m_searchFromScreenLine = 0; @@ -2255,6 +2264,7 @@ void FakeVimHandler::Private::recordInsertion(const QString &insert) m_oldPosition = pos; setTargetColumn(); } + m_oldDocumentLength = document()->characterCount(); } void FakeVimHandler::Private::ensureCursorVisible() @@ -3601,7 +3611,9 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input) // << input; QString savedCommand = g.dotCommand; g.dotCommand.clear(); + beginEditBlock(); replay(savedCommand); + endEditBlock(); resetCommandMode(); g.dotCommand = savedCommand; } else if (input.is('<') || input.is('>') || input.is('=')) { @@ -4305,16 +4317,17 @@ EventResult FakeVimHandler::Private::handleReplaceMode(const Input &input) EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) { bool clearLastInsertion = m_breakEditBlock; - if (m_oldPosition != position()) { + int pos2 = position(); + int len2 = document()->characterCount(); + if (m_oldPosition != pos2 || m_oldDocumentLength != len2) { if (clearLastInsertion) { clearLastInsertion = false; m_lastInsertion.clear(); } - recordInsertion(); + recordInsertion(guessInsertCommand(m_oldPosition, pos2, m_oldDocumentLength, len2)); } QString insert; - bool move = false; if (input.isEscape()) { // Repeat insertion [count] times. // One instance was already physically inserted while typing. @@ -4357,6 +4370,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) m_lastInsertion.remove(0, 1); g.dotCommand += m_lastInsertion + _("<ESC>"); enterCommandMode(); + setTargetColumn(); m_ctrlVActive = false; m_visualBlockInsert = false; } else if (m_ctrlVActive) { @@ -4381,86 +4395,82 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) insert = _("<INSERT>"); } else if (input.isKey(Key_Left)) { moveLeft(count()); - move = true; setTargetColumn(); } else if (input.isControl(Key_Left)) { moveToNextWordStart(count(), false, false); - move = true; setTargetColumn(); } else if (input.isKey(Key_Down)) { //removeAutomaticIndentation(); m_submode = NoSubMode; moveDown(count()); - move = true; } else if (input.isKey(Key_Up)) { //removeAutomaticIndentation(); m_submode = NoSubMode; moveUp(count()); - move = true; } else if (input.isKey(Key_Right)) { moveRight(count()); - move = true; setTargetColumn(); } else if (input.isControl(Key_Right)) { moveToNextWordStart(count(), false, true); moveRight(); // we need one more move since we are in insert mode - move = true; setTargetColumn(); } else if (input.isKey(Key_Home)) { moveToStartOfLine(); - move = true; setTargetColumn(); } else if (input.isKey(Key_End)) { if (count() > 1) moveDown(count() - 1); moveBehindEndOfLine(); - move = true; setTargetColumn(); m_targetColumn = -1; } else if (input.isReturn() || input.isControl('j') || input.isControl('m')) { - joinPreviousEditBlock(); - m_submode = NoSubMode; - insertNewLine(); - insert = _("\n"); - endEditBlock(); + if (!input.isReturn() || !handleInsertInEditor(input, &insert)) { + joinPreviousEditBlock(); + m_submode = NoSubMode; + insertNewLine(); + insert = _("\n"); + 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()); - } else { - setAnchor(); - cursor().deletePreviousChar(); + if (!handleInsertInEditor(input, &insert)) { + 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()); + } else { + setAnchor(); + cursor().deletePreviousChar(); + } } + insert = _("<BS>"); + endEditBlock(); } - insert = _("<BS>"); - endEditBlock(); } else if (input.isKey(Key_Delete)) { - joinPreviousEditBlock(); - cursor().deleteChar(); - insert = _("<DELETE>"); - endEditBlock(); + if (!handleInsertInEditor(input, &insert)) { + joinPreviousEditBlock(); + cursor().deleteChar(); + insert = _("<DELETE>"); + endEditBlock(); + } } else if (input.isKey(Key_PageDown) || input.isControl('f')) { removeAutomaticIndentation(); moveDown(count() * (linesOnScreen() - 2)); - move = true; } else if (input.isKey(Key_PageUp) || input.isControl('b')) { removeAutomaticIndentation(); moveUp(count() * (linesOnScreen() - 2)); - move = true; } else if (input.isKey(Key_Tab)) { m_justAutoIndented = 0; if (hasConfig(ConfigExpandTab)) { @@ -4511,16 +4521,20 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input) if (data && data->hasText()) insertInInsertMode(data->text()); insert = _("<S-INSERT>"); - } else if (!input.text().isEmpty()) { - insert = input.text(); - insertInInsertMode(insert); - insert.replace(_("<"), _("<LT>")); } else { - // We don't want fancy stuff in insert mode. - return EventHandled; + 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; + } + } } - if (move) { + if (insert.isNull()) { breakEditBlock(); m_oldPosition = position(); } else { @@ -6664,26 +6678,113 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace) void FakeVimHandler::Private::insertNewLine() { - if ( hasConfig(ConfigPassNewLine) ) { + if ( hasConfig(ConfigPassKeys) ) { QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, QLatin1String("\n")); + if (passEventToEditor(event)) + return; + } - removeEventFilter(); + insertText(QString::fromLatin1("\n")); + insertAutomaticIndentation(true); +} - QTextCursor tc = m_cursor; - tc.setPosition(tc.position()); - EDITOR(setTextCursor(tc)); - bool accepted = QApplication::sendEvent(editor(), &event); +bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *insert) +{ + if (!hasConfig(ConfigPassKeys) || m_editBlockLevel > 0) + return false; - installEventFilter(); + const int pos1 = position(); + const int len1 = lastPositionInDocument(); - if (accepted) { - setPosition(EDITOR(textCursor()).position()); - return; + QKeyEvent event(QEvent::KeyPress, input.key(), + static_cast<Qt::KeyboardModifiers>(input.modifiers()), input.text()); + if (!passEventToEditor(event)) + return false; + + const int pos2 = position(); + const int len2 = lastPositionInDocument(); + + *insert = guessInsertCommand(pos1, pos2, len1, len2); + + return true; +} + +bool FakeVimHandler::Private::passEventToEditor(QEvent &event) +{ + removeEventFilter(); + + QTextCursor tc = m_cursor; + tc.setPosition(tc.position()); + EDITOR(setTextCursor(tc)); + bool accepted = QApplication::sendEvent(editor(), &event); + + installEventFilter(); + + if (accepted) + setPosition(EDITOR(textCursor()).position()); + + 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 = 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 { + // 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. } - insertText(QString::fromLatin1("\n")); - insertAutomaticIndentation(true); + return insert; } QString FakeVimHandler::Private::lineContents(int line) const @@ -7000,6 +7101,7 @@ void FakeVimHandler::Private::enterInsertMode() m_subsubmode = NoSubSubMode; m_lastInsertion.clear(); m_oldPosition = position(); + m_oldDocumentLength = document()->characterCount(); if (g.returnToMode != InsertMode) { g.returnToMode = InsertMode; // If entering insert mode from command mode, m_targetColumn shouldn't be -1 (end of line). diff --git a/src/plugins/fakevim/fakevimoptions.ui b/src/plugins/fakevim/fakevimoptions.ui index 5114db3108765a38127654c1b952bfcad8ad82e7..b19a21a5ff7caf51214c13ed25e7ad0ebc5a9256 100644 --- a/src/plugins/fakevim/fakevimoptions.ui +++ b/src/plugins/fakevim/fakevimoptions.ui @@ -2,6 +2,14 @@ <ui version="4.0"> <class>FakeVim::Internal::FakeVimOptionPage</class> <widget class="QWidget" name="FakeVim::Internal::FakeVimOptionPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>580</width> + <height>479</height> + </rect> + </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <widget class="QCheckBox" name="checkBoxUseFakeVim"> @@ -113,12 +121,12 @@ </widget> </item> <item row="7" column="0"> - <widget class="QCheckBox" name="checkBoxPassNewLine"> + <widget class="QCheckBox" name="checkBoxPassKeys"> <property name="toolTip"> - <string>Let Qt Creator handle new lines so that comments or code blocks can be properly completed and expanded.</string> + <string>Let Qt Creator handle some key presses in insert mode so that code can be properly completed and expanded.</string> </property> <property name="text"> - <string>Pass new line</string> + <string>Pass keys in insert mode</string> </property> </widget> </item> @@ -363,7 +371,7 @@ <tabstop>checkBoxHlSearch</tabstop> <tabstop>checkBoxShowCmd</tabstop> <tabstop>checkBoxStartOfLine</tabstop> - <tabstop>checkBoxPassNewLine</tabstop> + <tabstop>checkBoxPassKeys</tabstop> <tabstop>checkBoxIncSearch</tabstop> <tabstop>checkBoxUseCoreSearch</tabstop> <tabstop>checkBoxIgnoreCase</tabstop> diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index 682aadf23563d7348bd4030a03100dab4d25dd79..a982c8121a777a3efb0c7f3acef3125cc3fe530c 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -292,8 +292,8 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent) m_ui.checkBoxSmartTab); m_group.insert(theFakeVimSetting(ConfigStartOfLine), m_ui.checkBoxStartOfLine); - m_group.insert(theFakeVimSetting(ConfigPassNewLine), - m_ui.checkBoxPassNewLine); + m_group.insert(theFakeVimSetting(ConfigPassKeys), + m_ui.checkBoxPassKeys); m_group.insert(theFakeVimSetting(ConfigTabStop), m_ui.spinBoxTabStop); m_group.insert(theFakeVimSetting(ConfigScrollOff), @@ -352,7 +352,7 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent) << sep << m_ui.checkBoxSmartCase->text() << sep << m_ui.checkBoxShowMarks->text() << sep << m_ui.checkBoxPassControlKey->text() - << sep << m_ui.checkBoxPassNewLine->text() + << sep << m_ui.checkBoxPassKeys->text() << sep << m_ui.checkBoxIgnoreCase->text() << sep << m_ui.checkBoxWrapScan->text() << sep << m_ui.checkBoxShowCmd->text() @@ -391,7 +391,7 @@ void FakeVimOptionPage::setQtStyle() m_ui.checkBoxSmartIndent->setChecked(true); m_ui.checkBoxIncSearch->setChecked(true); m_ui.lineEditBackspace->setText(_("indent,eol,start")); - m_ui.checkBoxPassNewLine->setChecked(true); + m_ui.checkBoxPassKeys->setChecked(true); } void FakeVimOptionPage::setPlainStyle() @@ -404,7 +404,7 @@ void FakeVimOptionPage::setPlainStyle() m_ui.checkBoxSmartIndent->setChecked(false); m_ui.checkBoxIncSearch->setChecked(false); m_ui.lineEditBackspace->setText(QString()); - m_ui.checkBoxPassNewLine->setChecked(false); + m_ui.checkBoxPassKeys->setChecked(false); } void FakeVimOptionPage::openVimRc()