Commit 1bea9b98 authored by hluk's avatar hluk Committed by hjk
Browse files

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: default avatarhjk <hjk121@nokiamail.com>
parent 90c5f424
......@@ -274,6 +274,7 @@ void FakeVimPlugin::setup(TestData *data)
{
setupTest(&data->title, &data->handler, &data->edit);
data->reset();
data->doCommand("set nopasskeys | set nopasscontrolkey");
}
......
......@@ -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"));
......
......@@ -93,7 +93,7 @@ enum FakeVimSettingsCode
// other actions
ConfigShowMarks,
ConfigPassControlKey,
ConfigPassNewLine,
ConfigPassKeys,
ConfigClipboard,
ConfigShowCmd,
ConfigScrollOff
......
......@@ -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).
......
......@@ -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>
......
......@@ -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()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment