FakeVim: Option 'passcharacters' (not in Vim)

Option to pass some key presses in insert mode to editor widget
(replaces 'passnewline'). This allows to rename symbols in insert mode,
complete parenthesis blocks, expand comments etc.

Macro expansion and code-completion works with dot command.

Task-number:QTCREATORBUG-4828
Change-Id: I5ff43818d4f7f183cd6f4ed8cc3a4586469ab65d
Reviewed-by: hjk <hjk121@nokiamail.com>
This commit is contained in:
hluk
2013-04-01 12:03:22 +02:00
committed by hjk
parent 90c5f4244b
commit 1bea9b98fb
6 changed files with 189 additions and 78 deletions

View File

@@ -274,6 +274,7 @@ void FakeVimPlugin::setup(TestData *data)
{ {
setupTest(&data->title, &data->handler, &data->edit); setupTest(&data->title, &data->handler, &data->edit);
data->reset(); data->reset();
data->doCommand("set nopasskeys | set nopasscontrolkey");
} }

View File

@@ -180,7 +180,7 @@ FakeVimSettings *theFakeVimSettings()
#endif #endif
createAction(s, ConfigShowMarks, false, _("ShowMarks"), _("sm")); createAction(s, ConfigShowMarks, false, _("ShowMarks"), _("sm"));
createAction(s, ConfigPassControlKey, false, _("PassControlKey"), _("pck")); createAction(s, ConfigPassControlKey, false, _("PassControlKey"), _("pck"));
createAction(s, ConfigPassNewLine, false, _("PassNewLine"), _("pnl")); createAction(s, ConfigPassKeys, true, _("PassKeys"), _("pk"));
// Emulated Vim setting // Emulated Vim setting
createAction(s, ConfigStartOfLine, true, _("StartOfLine"), _("sol")); createAction(s, ConfigStartOfLine, true, _("StartOfLine"), _("sol"));

View File

@@ -93,7 +93,7 @@ enum FakeVimSettingsCode
// other actions // other actions
ConfigShowMarks, ConfigShowMarks,
ConfigPassControlKey, ConfigPassControlKey,
ConfigPassNewLine, ConfigPassKeys,
ConfigClipboard, ConfigClipboard,
ConfigShowCmd, ConfigShowCmd,
ConfigScrollOff ConfigScrollOff

View File

@@ -949,6 +949,8 @@ public:
int key() const { return m_key; } int key() const { return m_key; }
int modifiers() const { return m_modifiers; }
// Return raw character for macro recording or dot command. // Return raw character for macro recording or dot command.
QChar raw() const QChar raw() const
{ {
@@ -1708,6 +1710,7 @@ 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;
QString m_mvcount; QString m_mvcount;
QString m_opcount; QString m_opcount;
@@ -1772,6 +1775,11 @@ public:
void insertNewLine(); 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 // undo handling
int revision() const { return document()->availableUndoSteps(); } int revision() const { return document()->availableUndoSteps(); }
void undoRedo(bool undo); void undoRedo(bool undo);
@@ -1974,6 +1982,7 @@ void FakeVimHandler::Private::init()
m_oldExternalAnchor = -1; m_oldExternalAnchor = -1;
m_oldExternalPosition = -1; m_oldExternalPosition = -1;
m_oldPosition = -1; m_oldPosition = -1;
m_oldDocumentLength = -1;
m_breakEditBlock = false; m_breakEditBlock = false;
m_searchStartPosition = 0; m_searchStartPosition = 0;
m_searchFromScreenLine = 0; m_searchFromScreenLine = 0;
@@ -2255,6 +2264,7 @@ void FakeVimHandler::Private::recordInsertion(const QString &insert)
m_oldPosition = pos; m_oldPosition = pos;
setTargetColumn(); setTargetColumn();
} }
m_oldDocumentLength = document()->characterCount();
} }
void FakeVimHandler::Private::ensureCursorVisible() void FakeVimHandler::Private::ensureCursorVisible()
@@ -3601,7 +3611,9 @@ bool FakeVimHandler::Private::handleNoSubMode(const Input &input)
// << input; // << input;
QString savedCommand = g.dotCommand; QString savedCommand = g.dotCommand;
g.dotCommand.clear(); g.dotCommand.clear();
beginEditBlock();
replay(savedCommand); replay(savedCommand);
endEditBlock();
resetCommandMode(); resetCommandMode();
g.dotCommand = savedCommand; g.dotCommand = savedCommand;
} else if (input.is('<') || input.is('>') || input.is('=')) { } 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) EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
{ {
bool clearLastInsertion = m_breakEditBlock; bool clearLastInsertion = m_breakEditBlock;
if (m_oldPosition != position()) { int pos2 = position();
int len2 = document()->characterCount();
if (m_oldPosition != pos2 || m_oldDocumentLength != len2) {
if (clearLastInsertion) { if (clearLastInsertion) {
clearLastInsertion = false; clearLastInsertion = false;
m_lastInsertion.clear(); m_lastInsertion.clear();
} }
recordInsertion(); recordInsertion(guessInsertCommand(m_oldPosition, pos2, m_oldDocumentLength, len2));
} }
QString insert; QString insert;
bool move = false;
if (input.isEscape()) { if (input.isEscape()) {
// Repeat insertion [count] times. // Repeat insertion [count] times.
// One instance was already physically inserted while typing. // One instance was already physically inserted while typing.
@@ -4357,6 +4370,7 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
m_lastInsertion.remove(0, 1); m_lastInsertion.remove(0, 1);
g.dotCommand += m_lastInsertion + _("<ESC>"); g.dotCommand += m_lastInsertion + _("<ESC>");
enterCommandMode(); enterCommandMode();
setTargetColumn();
m_ctrlVActive = false; m_ctrlVActive = false;
m_visualBlockInsert = false; m_visualBlockInsert = false;
} else if (m_ctrlVActive) { } else if (m_ctrlVActive) {
@@ -4381,86 +4395,82 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
insert = _("<INSERT>"); insert = _("<INSERT>");
} else if (input.isKey(Key_Left)) { } else if (input.isKey(Key_Left)) {
moveLeft(count()); moveLeft(count());
move = true;
setTargetColumn(); setTargetColumn();
} else if (input.isControl(Key_Left)) { } else if (input.isControl(Key_Left)) {
moveToNextWordStart(count(), false, false); moveToNextWordStart(count(), false, false);
move = true;
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_Down)) { } else if (input.isKey(Key_Down)) {
//removeAutomaticIndentation(); //removeAutomaticIndentation();
m_submode = NoSubMode; m_submode = NoSubMode;
moveDown(count()); moveDown(count());
move = true;
} else if (input.isKey(Key_Up)) { } else if (input.isKey(Key_Up)) {
//removeAutomaticIndentation(); //removeAutomaticIndentation();
m_submode = NoSubMode; m_submode = NoSubMode;
moveUp(count()); moveUp(count());
move = true;
} else if (input.isKey(Key_Right)) { } else if (input.isKey(Key_Right)) {
moveRight(count()); moveRight(count());
move = true;
setTargetColumn(); setTargetColumn();
} else if (input.isControl(Key_Right)) { } else if (input.isControl(Key_Right)) {
moveToNextWordStart(count(), false, true); moveToNextWordStart(count(), false, true);
moveRight(); // we need one more move since we are in insert mode moveRight(); // we need one more move since we are in insert mode
move = true;
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_Home)) { } else if (input.isKey(Key_Home)) {
moveToStartOfLine(); moveToStartOfLine();
move = true;
setTargetColumn(); setTargetColumn();
} else if (input.isKey(Key_End)) { } else if (input.isKey(Key_End)) {
if (count() > 1) if (count() > 1)
moveDown(count() - 1); moveDown(count() - 1);
moveBehindEndOfLine(); moveBehindEndOfLine();
move = true;
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')) {
joinPreviousEditBlock(); if (!input.isReturn() || !handleInsertInEditor(input, &insert)) {
m_submode = NoSubMode; joinPreviousEditBlock();
insertNewLine(); m_submode = NoSubMode;
insert = _("\n"); insertNewLine();
endEditBlock(); insert = _("\n");
} else if (input.isBackspace()) { endEditBlock();
joinPreviousEditBlock(); }
m_justAutoIndented = 0; } else if (input.isBackspace()) {
if (!m_lastInsertion.isEmpty() if (!handleInsertInEditor(input, &insert)) {
|| hasConfig(ConfigBackspace, "start") joinPreviousEditBlock();
|| hasConfig(ConfigBackspace, "2")) { m_justAutoIndented = 0;
const int line = cursorLine() + 1; if (!m_lastInsertion.isEmpty()
const Column col = cursorColumn(); || hasConfig(ConfigBackspace, "start")
QString data = lineContents(line); || hasConfig(ConfigBackspace, "2")) {
const Column ind = indentation(data); const int line = cursorLine() + 1;
if (col.logical <= ind.logical && col.logical const Column col = cursorColumn();
&& startsWithWhitespace(data, col.physical)) { QString data = lineContents(line);
const int ts = config(ConfigTabStop).toInt(); const Column ind = indentation(data);
const int newl = col.logical - 1 - (col.logical - 1) % ts; if (col.logical <= ind.logical && col.logical
const QString prefix = tabExpand(newl); && startsWithWhitespace(data, col.physical)) {
setLineContents(line, prefix + data.mid(col.physical)); const int ts = config(ConfigTabStop).toInt();
moveToStartOfLine(); const int newl = col.logical - 1 - (col.logical - 1) % ts;
moveRight(prefix.size()); const QString prefix = tabExpand(newl);
} else { setLineContents(line, prefix + data.mid(col.physical));
setAnchor(); moveToStartOfLine();
cursor().deletePreviousChar(); moveRight(prefix.size());
} } else {
setAnchor();
cursor().deletePreviousChar();
}
}
insert = _("<BS>");
endEditBlock();
} }
insert = _("<BS>");
endEditBlock();
} else if (input.isKey(Key_Delete)) { } else if (input.isKey(Key_Delete)) {
joinPreviousEditBlock(); if (!handleInsertInEditor(input, &insert)) {
cursor().deleteChar(); joinPreviousEditBlock();
insert = _("<DELETE>"); cursor().deleteChar();
endEditBlock(); insert = _("<DELETE>");
endEditBlock();
}
} else if (input.isKey(Key_PageDown) || input.isControl('f')) { } else if (input.isKey(Key_PageDown) || input.isControl('f')) {
removeAutomaticIndentation(); removeAutomaticIndentation();
moveDown(count() * (linesOnScreen() - 2)); moveDown(count() * (linesOnScreen() - 2));
move = true;
} else if (input.isKey(Key_PageUp) || input.isControl('b')) { } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
removeAutomaticIndentation(); removeAutomaticIndentation();
moveUp(count() * (linesOnScreen() - 2)); moveUp(count() * (linesOnScreen() - 2));
move = true;
} else if (input.isKey(Key_Tab)) { } else if (input.isKey(Key_Tab)) {
m_justAutoIndented = 0; m_justAutoIndented = 0;
if (hasConfig(ConfigExpandTab)) { if (hasConfig(ConfigExpandTab)) {
@@ -4511,16 +4521,20 @@ EventResult FakeVimHandler::Private::handleInsertMode(const Input &input)
if (data && data->hasText()) if (data && data->hasText())
insertInInsertMode(data->text()); insertInInsertMode(data->text());
insert = _("<S-INSERT>"); insert = _("<S-INSERT>");
} else if (!input.text().isEmpty()) {
insert = input.text();
insertInInsertMode(insert);
insert.replace(_("<"), _("<LT>"));
} else { } else {
// We don't want fancy stuff in insert mode. if (!handleInsertInEditor(input, &insert)) {
return EventHandled; 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(); breakEditBlock();
m_oldPosition = position(); m_oldPosition = position();
} else { } else {
@@ -6664,28 +6678,115 @@ void FakeVimHandler::Private::joinLines(int count, bool preserveSpace)
void FakeVimHandler::Private::insertNewLine() void FakeVimHandler::Private::insertNewLine()
{ {
if ( hasConfig(ConfigPassNewLine) ) { if ( 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))
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; return;
}
} }
insertText(QString::fromLatin1("\n")); insertText(QString::fromLatin1("\n"));
insertAutomaticIndentation(true); insertAutomaticIndentation(true);
} }
bool FakeVimHandler::Private::handleInsertInEditor(const Input &input, QString *insert)
{
if (!hasConfig(ConfigPassKeys) || m_editBlockLevel > 0)
return false;
const int pos1 = position();
const int len1 = lastPositionInDocument();
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.
}
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();
@@ -7000,6 +7101,7 @@ void FakeVimHandler::Private::enterInsertMode()
m_subsubmode = NoSubSubMode; m_subsubmode = NoSubSubMode;
m_lastInsertion.clear(); m_lastInsertion.clear();
m_oldPosition = position(); m_oldPosition = position();
m_oldDocumentLength = document()->characterCount();
if (g.returnToMode != InsertMode) { if (g.returnToMode != InsertMode) {
g.returnToMode = 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).

View File

@@ -2,6 +2,14 @@
<ui version="4.0"> <ui version="4.0">
<class>FakeVim::Internal::FakeVimOptionPage</class> <class>FakeVim::Internal::FakeVimOptionPage</class>
<widget class="QWidget" name="FakeVim::Internal::FakeVimOptionPage"> <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"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QCheckBox" name="checkBoxUseFakeVim"> <widget class="QCheckBox" name="checkBoxUseFakeVim">
@@ -113,12 +121,12 @@
</widget> </widget>
</item> </item>
<item row="7" column="0"> <item row="7" column="0">
<widget class="QCheckBox" name="checkBoxPassNewLine"> <widget class="QCheckBox" name="checkBoxPassKeys">
<property name="toolTip"> <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>
<property name="text"> <property name="text">
<string>Pass new line</string> <string>Pass keys in insert mode</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -363,7 +371,7 @@
<tabstop>checkBoxHlSearch</tabstop> <tabstop>checkBoxHlSearch</tabstop>
<tabstop>checkBoxShowCmd</tabstop> <tabstop>checkBoxShowCmd</tabstop>
<tabstop>checkBoxStartOfLine</tabstop> <tabstop>checkBoxStartOfLine</tabstop>
<tabstop>checkBoxPassNewLine</tabstop> <tabstop>checkBoxPassKeys</tabstop>
<tabstop>checkBoxIncSearch</tabstop> <tabstop>checkBoxIncSearch</tabstop>
<tabstop>checkBoxUseCoreSearch</tabstop> <tabstop>checkBoxUseCoreSearch</tabstop>
<tabstop>checkBoxIgnoreCase</tabstop> <tabstop>checkBoxIgnoreCase</tabstop>

View File

@@ -292,8 +292,8 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent)
m_ui.checkBoxSmartTab); m_ui.checkBoxSmartTab);
m_group.insert(theFakeVimSetting(ConfigStartOfLine), m_group.insert(theFakeVimSetting(ConfigStartOfLine),
m_ui.checkBoxStartOfLine); m_ui.checkBoxStartOfLine);
m_group.insert(theFakeVimSetting(ConfigPassNewLine), m_group.insert(theFakeVimSetting(ConfigPassKeys),
m_ui.checkBoxPassNewLine); m_ui.checkBoxPassKeys);
m_group.insert(theFakeVimSetting(ConfigTabStop), m_group.insert(theFakeVimSetting(ConfigTabStop),
m_ui.spinBoxTabStop); m_ui.spinBoxTabStop);
m_group.insert(theFakeVimSetting(ConfigScrollOff), m_group.insert(theFakeVimSetting(ConfigScrollOff),
@@ -352,7 +352,7 @@ QWidget *FakeVimOptionPage::createPage(QWidget *parent)
<< sep << m_ui.checkBoxSmartCase->text() << sep << m_ui.checkBoxSmartCase->text()
<< sep << m_ui.checkBoxShowMarks->text() << sep << m_ui.checkBoxShowMarks->text()
<< sep << m_ui.checkBoxPassControlKey->text() << sep << m_ui.checkBoxPassControlKey->text()
<< sep << m_ui.checkBoxPassNewLine->text() << sep << m_ui.checkBoxPassKeys->text()
<< sep << m_ui.checkBoxIgnoreCase->text() << sep << m_ui.checkBoxIgnoreCase->text()
<< sep << m_ui.checkBoxWrapScan->text() << sep << m_ui.checkBoxWrapScan->text()
<< sep << m_ui.checkBoxShowCmd->text() << sep << m_ui.checkBoxShowCmd->text()
@@ -391,7 +391,7 @@ void FakeVimOptionPage::setQtStyle()
m_ui.checkBoxSmartIndent->setChecked(true); m_ui.checkBoxSmartIndent->setChecked(true);
m_ui.checkBoxIncSearch->setChecked(true); m_ui.checkBoxIncSearch->setChecked(true);
m_ui.lineEditBackspace->setText(_("indent,eol,start")); m_ui.lineEditBackspace->setText(_("indent,eol,start"));
m_ui.checkBoxPassNewLine->setChecked(true); m_ui.checkBoxPassKeys->setChecked(true);
} }
void FakeVimOptionPage::setPlainStyle() void FakeVimOptionPage::setPlainStyle()
@@ -404,7 +404,7 @@ void FakeVimOptionPage::setPlainStyle()
m_ui.checkBoxSmartIndent->setChecked(false); m_ui.checkBoxSmartIndent->setChecked(false);
m_ui.checkBoxIncSearch->setChecked(false); m_ui.checkBoxIncSearch->setChecked(false);
m_ui.lineEditBackspace->setText(QString()); m_ui.lineEditBackspace->setText(QString());
m_ui.checkBoxPassNewLine->setChecked(false); m_ui.checkBoxPassKeys->setChecked(false);
} }
void FakeVimOptionPage::openVimRc() void FakeVimOptionPage::openVimRc()