FakeVim: Accept suggestion with Tab Key

* Changed signals to callbacks as only one receiver was ever added
* Added "tabPressedInInsertMode" callback to allow accepting
  a suggestion with the Tab Key

Fixes: QTCREATORBUG-28830
Change-Id: Ie70ba595b8802b6100fff495164d8e0471b1354c
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-04-27 12:18:07 +02:00
parent 8f345bbc35
commit 015d12ccf3
4 changed files with 103 additions and 89 deletions

View File

@@ -5459,6 +5459,7 @@ void FakeVimHandler::Private::handleInsertMode(const Input &input)
} else if (input.isKey(Key_PageUp) || input.isControl('b')) { } else if (input.isKey(Key_PageUp) || input.isControl('b')) {
movePageUp(); movePageUp();
} else if (input.isKey(Key_Tab)) { } else if (input.isKey(Key_Tab)) {
if (q->tabPressedInInsertMode()) {
m_buffer->insertState.insertingSpaces = true; m_buffer->insertState.insertingSpaces = true;
if (s.expandTab.value()) { if (s.expandTab.value()) {
const int ts = s.tabStop.value(); const int ts = s.tabStop.value();
@@ -5469,6 +5470,7 @@ void FakeVimHandler::Private::handleInsertMode(const Input &input)
insertInInsertMode(input.raw()); insertInInsertMode(input.raw());
} }
m_buffer->insertState.insertingSpaces = false; m_buffer->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
const int shift = s.shiftWidth.value(); const int shift = s.shiftWidth.value();

View File

@@ -61,23 +61,30 @@ enum MessageLevel
MessageShowCmd // partial command MessageShowCmd // partial command
}; };
template <typename Type> template<typename>
class Signal class Callback;
template<typename R, typename... Params>
class Callback<R(Params...)>
{ {
public: public:
using Callable = std::function<Type>; static constexpr auto IsVoidReturnType = std::is_same_v<R, void>;
using Function = std::function<R(Params...)>;
void set(const Function &callable) { m_callable = callable; }
void connect(const Callable &callable) { m_callables.push_back(callable); } R operator()(Params... params)
template <typename ...Args>
void operator()(Args ...args) const
{ {
for (const Callable &callable : m_callables) if (!m_callable)
callable(args...); return R();
if constexpr (IsVoidReturnType)
m_callable(std::forward<Params>(params)...);
else
return m_callable(std::forward<Params>(params)...);
} }
private: private:
std::vector<Callable> m_callables; Function m_callable;
}; };
class FakeVimHandler : public QObject class FakeVimHandler : public QObject
@@ -131,33 +138,35 @@ public:
bool eventFilter(QObject *ob, QEvent *ev) override; bool eventFilter(QObject *ob, QEvent *ev) override;
Signal<void(const QString &msg, int cursorPos, int anchorPos, int messageLevel)> commandBufferChanged; Callback<void(const QString &msg, int cursorPos, int anchorPos, int messageLevel)>
Signal<void(const QString &msg)> statusDataChanged; commandBufferChanged;
Signal<void(const QString &msg)> extraInformationChanged; Callback<void(const QString &msg)> statusDataChanged;
Signal<void(const QList<QTextEdit::ExtraSelection> &selection)> selectionChanged; Callback<void(const QString &msg)> extraInformationChanged;
Signal<void(const QString &needle)> highlightMatches; Callback<void(const QList<QTextEdit::ExtraSelection> &selection)> selectionChanged;
Signal<void(bool *moved, bool *forward, QTextCursor *cursor)> moveToMatchingParenthesis; Callback<void(const QString &needle)> highlightMatches;
Signal<void(bool *result, QChar c)> checkForElectricCharacter; Callback<void(bool *moved, bool *forward, QTextCursor *cursor)> moveToMatchingParenthesis;
Signal<void(int beginLine, int endLine, QChar typedChar)> indentRegion; Callback<void(bool *result, QChar c)> checkForElectricCharacter;
Signal<void(const QString &needle, bool forward)> simpleCompletionRequested; Callback<void(int beginLine, int endLine, QChar typedChar)> indentRegion;
Signal<void(const QString &key, int count)> windowCommandRequested; Callback<void(const QString &needle, bool forward)> simpleCompletionRequested;
Signal<void(bool reverse)> findRequested; Callback<void(const QString &key, int count)> windowCommandRequested;
Signal<void(bool reverse)> findNextRequested; Callback<void(bool reverse)> findRequested;
Signal<void(bool *handled, const ExCommand &cmd)> handleExCommandRequested; Callback<void(bool reverse)> findNextRequested;
Signal<void()> requestDisableBlockSelection; Callback<void(bool *handled, const ExCommand &cmd)> handleExCommandRequested;
Signal<void(const QTextCursor &cursor)> requestSetBlockSelection; Callback<void()> requestDisableBlockSelection;
Signal<void(QTextCursor *cursor)> requestBlockSelection; Callback<void(const QTextCursor &cursor)> requestSetBlockSelection;
Signal<void(bool *on)> requestHasBlockSelection; Callback<void(QTextCursor *cursor)> requestBlockSelection;
Signal<void(int depth)> foldToggle; Callback<void(bool *on)> requestHasBlockSelection;
Signal<void(bool fold)> foldAll; Callback<void(int depth)> foldToggle;
Signal<void(int depth, bool dofold)> fold; Callback<void(bool fold)> foldAll;
Signal<void(int count, bool current)> foldGoTo; Callback<void(int depth, bool dofold)> fold;
Signal<void(QChar mark, bool backTickMode, const QString &fileName)> requestJumpToLocalMark; Callback<void(int count, bool current)> foldGoTo;
Signal<void(QChar mark, bool backTickMode, const QString &fileName)> requestJumpToGlobalMark; Callback<void(QChar mark, bool backTickMode, const QString &fileName)> requestJumpToLocalMark;
Signal<void()> completionRequested; Callback<void(QChar mark, bool backTickMode, const QString &fileName)> requestJumpToGlobalMark;
Signal<void()> tabPreviousRequested; Callback<void()> completionRequested;
Signal<void()> tabNextRequested; Callback<void()> tabPreviousRequested;
Signal<void(bool insertMode)> modeChanged; Callback<void()> tabNextRequested;
Callback<void(bool insertMode)> modeChanged;
Callback<bool()> tabPressedInInsertMode;
public: public:
class Private; class Private;

View File

@@ -1586,7 +1586,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
new DeferredDeleter(widget, handler); new DeferredDeleter(widget, handler);
m_editorToHandler[editor].handler = handler; m_editorToHandler[editor].handler = handler;
handler->extraInformationChanged.connect([this](const QString &text) { handler->extraInformationChanged.set([this](const QString &text) {
EditorManager::splitSideBySide(); EditorManager::splitSideBySide();
QString title = "stdout.txt"; QString title = "stdout.txt";
IEditor *iedit = EditorManager::openEditorWithContents(Id(), &title, text.toUtf8()); IEditor *iedit = EditorManager::openEditorWithContents(Id(), &title, text.toUtf8());
@@ -1596,17 +1596,27 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
handler->handleCommand("0"); handler->handleCommand("0");
}); });
handler->commandBufferChanged handler->commandBufferChanged.set(
.connect([this, handler](const QString &contents, int cursorPos, int anchorPos, int messageLevel) { [this, handler](const QString &contents, int cursorPos, int anchorPos, int messageLevel) {
showCommandBuffer(handler, contents, cursorPos, anchorPos, messageLevel); showCommandBuffer(handler, contents, cursorPos, anchorPos, messageLevel);
}); });
handler->selectionChanged.connect([tew](const QList<QTextEdit::ExtraSelection> &selection) { handler->selectionChanged.set([tew](const QList<QTextEdit::ExtraSelection> &selection) {
if (tew) if (tew)
tew->setExtraSelections(TextEditorWidget::FakeVimSelection, selection); tew->setExtraSelections(TextEditorWidget::FakeVimSelection, selection);
}); });
handler->modeChanged.connect([tew, this, editor](bool insertMode) { handler->tabPressedInInsertMode.set([tew]() {
auto suggestion = tew->currentSuggestion();
if (suggestion) {
suggestion->apply();
return false;
}
return true;
});
handler->modeChanged.set([tew, this, editor](bool insertMode) {
HandlerAndData &handlerAndData = m_editorToHandler[editor]; HandlerAndData &handlerAndData = m_editorToHandler[editor];
// We don't want to show suggestions unless we are in insert mode. // We don't want to show suggestions unless we are in insert mode.
@@ -1617,7 +1627,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
tew->clearSuggestion(); tew->clearSuggestion();
}); });
handler->highlightMatches.connect([](const QString &needle) { handler->highlightMatches.set([](const QString &needle) {
for (IEditor *editor : EditorManager::visibleEditors()) { for (IEditor *editor : EditorManager::visibleEditors()) {
QWidget *w = editor->widget(); QWidget *w = editor->widget();
if (auto find = Aggregation::query<IFindSupport>(w)) if (auto find = Aggregation::query<IFindSupport>(w))
@@ -1625,7 +1635,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->moveToMatchingParenthesis.connect([](bool *moved, bool *forward, QTextCursor *cursor) { handler->moveToMatchingParenthesis.set([](bool *moved, bool *forward, QTextCursor *cursor) {
*moved = false; *moved = false;
bool undoFakeEOL = false; bool undoFakeEOL = false;
@@ -1658,7 +1668,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->indentRegion.connect([tew](int beginBlock, int endBlock, QChar typedChar) { handler->indentRegion.set([tew](int beginBlock, int endBlock, QChar typedChar) {
if (!tew) if (!tew)
return; return;
@@ -1691,17 +1701,17 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->checkForElectricCharacter.connect([tew](bool *result, QChar c) { handler->checkForElectricCharacter.set([tew](bool *result, QChar c) {
if (tew) if (tew)
*result = tew->textDocument()->indenter()->isElectricCharacter(c); *result = tew->textDocument()->indenter()->isElectricCharacter(c);
}); });
handler->requestDisableBlockSelection.connect([tew] { handler->requestDisableBlockSelection.set([tew] {
if (tew) if (tew)
tew->setTextCursor(tew->textCursor()); tew->setTextCursor(tew->textCursor());
}); });
handler->requestSetBlockSelection.connect([tew](const QTextCursor &cursor) { handler->requestSetBlockSelection.set([tew](const QTextCursor &cursor) {
if (tew) { if (tew) {
const TabSettings &tabs = tew->textDocument()->tabSettings(); const TabSettings &tabs = tew->textDocument()->tabSettings();
MultiTextCursor mtc; MultiTextCursor mtc;
@@ -1725,7 +1735,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->requestBlockSelection.connect([tew](QTextCursor *cursor) { handler->requestBlockSelection.set([tew](QTextCursor *cursor) {
if (tew && cursor) { if (tew && cursor) {
MultiTextCursor mtc = tew->multiTextCursor(); MultiTextCursor mtc = tew->multiTextCursor();
*cursor = mtc.cursors().first(); *cursor = mtc.cursors().first();
@@ -1733,16 +1743,16 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->requestHasBlockSelection.connect([tew](bool *on) { handler->requestHasBlockSelection.set([tew](bool *on) {
if (tew && on) if (tew && on)
*on = tew->multiTextCursor().hasMultipleCursors(); *on = tew->multiTextCursor().hasMultipleCursors();
}); });
handler->simpleCompletionRequested.connect([this, handler](const QString &needle, bool forward) { handler->simpleCompletionRequested.set([this, handler](const QString &needle, bool forward) {
runData->wordProvider.setActive(needle, forward, handler); runData->wordProvider.setActive(needle, forward, handler);
}); });
handler->windowCommandRequested.connect([this, handler](const QString &map, int count) { handler->windowCommandRequested.set([this, handler](const QString &map, int count) {
// normalize mapping // normalize mapping
const QString key = map.toUpper(); const QString key = map.toUpper();
@@ -1772,22 +1782,22 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
qDebug() << "UNKNOWN WINDOW COMMAND: <C-W>" << map; qDebug() << "UNKNOWN WINDOW COMMAND: <C-W>" << map;
}); });
handler->findRequested.connect([](bool reverse) { handler->findRequested.set([](bool reverse) {
Find::setUseFakeVim(true); Find::setUseFakeVim(true);
Find::openFindToolBar(reverse ? Find::FindBackwardDirection Find::openFindToolBar(reverse ? Find::FindBackwardDirection
: Find::FindForwardDirection); : Find::FindForwardDirection);
}); });
handler->findNextRequested.connect([](bool reverse) { handler->findNextRequested.set([](bool reverse) {
triggerAction(reverse ? Core::Constants::FIND_PREVIOUS : Core::Constants::FIND_NEXT); triggerAction(reverse ? Core::Constants::FIND_PREVIOUS : Core::Constants::FIND_NEXT);
}); });
handler->foldToggle.connect([this, handler](int depth) { handler->foldToggle.set([this, handler](int depth) {
QTextBlock block = handler->textCursor().block(); QTextBlock block = handler->textCursor().block();
fold(handler, depth, !TextDocumentLayout::isFolded(block)); fold(handler, depth, !TextDocumentLayout::isFolded(block));
}); });
handler->foldAll.connect([handler](bool fold) { handler->foldAll.set([handler](bool fold) {
QTextDocument *document = handler->textCursor().document(); QTextDocument *document = handler->textCursor().document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(document->documentLayout()); auto documentLayout = qobject_cast<TextDocumentLayout*>(document->documentLayout());
QTC_ASSERT(documentLayout, return); QTC_ASSERT(documentLayout, return);
@@ -1802,11 +1812,9 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
documentLayout->emitDocumentSizeChanged(); documentLayout->emitDocumentSizeChanged();
}); });
handler->fold.connect([this, handler](int depth, bool dofold) { handler->fold.set([this, handler](int depth, bool dofold) { fold(handler, depth, dofold); });
fold(handler, depth, dofold);
});
handler->foldGoTo.connect([handler](int count, bool current) { handler->foldGoTo.set([handler](int count, bool current) {
QTextCursor tc = handler->textCursor(); QTextCursor tc = handler->textCursor();
QTextBlock block = tc.block(); QTextBlock block = tc.block();
@@ -1858,7 +1866,7 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->requestJumpToGlobalMark.connect( handler->requestJumpToGlobalMark.set(
[this](QChar mark, bool backTickMode, const QString &fileName) { [this](QChar mark, bool backTickMode, const QString &fileName) {
if (IEditor *iedit = EditorManager::openEditor(FilePath::fromString(fileName))) { if (IEditor *iedit = EditorManager::openEditor(FilePath::fromString(fileName))) {
if (FakeVimHandler *handler = m_editorToHandler.value(iedit, {}).handler) if (FakeVimHandler *handler = m_editorToHandler.value(iedit, {}).handler)
@@ -1866,19 +1874,15 @@ void FakeVimPluginPrivate::editorOpened(IEditor *editor)
} }
}); });
handler->handleExCommandRequested.connect([this, handler](bool *handled, const ExCommand &cmd) { handler->handleExCommandRequested.set([this, handler](bool *handled, const ExCommand &cmd) {
handleExCommand(handler, handled, cmd); handleExCommand(handler, handled, cmd);
}); });
handler->tabNextRequested.connect([] { handler->tabNextRequested.set([] { triggerAction(Core::Constants::GOTONEXTINHISTORY); });
triggerAction(Core::Constants::GOTONEXTINHISTORY);
});
handler->tabPreviousRequested.connect([] { handler->tabPreviousRequested.set([] { triggerAction(Core::Constants::GOTOPREVINHISTORY); });
triggerAction(Core::Constants::GOTOPREVINHISTORY);
});
handler->completionRequested.connect([this, tew] { handler->completionRequested.set([this, tew] {
if (tew) if (tew)
tew->invokeAssist(Completion, &runData->wordProvider); tew->invokeAssist(Completion, &runData->wordProvider);
}); });

View File

@@ -187,12 +187,12 @@ int main(int argc, char *argv[])
// Create FakeVimHandler instance which will emulate Vim behavior in editor widget. // Create FakeVimHandler instance which will emulate Vim behavior in editor widget.
FakeVimHandler handler(editor, nullptr); FakeVimHandler handler(editor, nullptr);
handler.commandBufferChanged.connect([&](const QString &msg, int cursorPos, int, int) { handler.commandBufferChanged.set([&](const QString &msg, int cursorPos, int, int) {
statusData.setStatusMessage(msg, cursorPos); statusData.setStatusMessage(msg, cursorPos);
mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); mainWindow.statusBar()->showMessage(statusData.currentStatusLine());
}); });
handler.selectionChanged.connect([&handler](const QList<QTextEdit::ExtraSelection> &s) { handler.selectionChanged.set([&handler](const QList<QTextEdit::ExtraSelection> &s) {
QWidget *widget = handler.widget(); QWidget *widget = handler.widget();
if (auto ed = qobject_cast<QPlainTextEdit *>(widget)) if (auto ed = qobject_cast<QPlainTextEdit *>(widget))
ed->setExtraSelections(s); ed->setExtraSelections(s);
@@ -200,21 +200,20 @@ int main(int argc, char *argv[])
ed->setExtraSelections(s); ed->setExtraSelections(s);
}); });
handler.extraInformationChanged.connect([&](const QString &info) { handler.extraInformationChanged.set([&](const QString &info) {
statusData.setStatusInfo(info); statusData.setStatusInfo(info);
mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); mainWindow.statusBar()->showMessage(statusData.currentStatusLine());
}); });
handler.statusDataChanged.connect([&](const QString &info) { handler.statusDataChanged.set([&](const QString &info) {
statusData.setStatusInfo(info); statusData.setStatusInfo(info);
mainWindow.statusBar()->showMessage(statusData.currentStatusLine()); mainWindow.statusBar()->showMessage(statusData.currentStatusLine());
}); });
handler.highlightMatches.connect([&](const QString &needle) { handler.highlightMatches.set(
highlightMatches(handler.widget(), needle); [&](const QString &needle) { highlightMatches(handler.widget(), needle); });
});
handler.handleExCommandRequested.connect([](bool *handled, const ExCommand &cmd) { handler.handleExCommandRequested.set([](bool *handled, const ExCommand &cmd) {
if (cmd.matches("q", "quit") || cmd.matches("qa", "qall")) { if (cmd.matches("q", "quit") || cmd.matches("qa", "qall")) {
QApplication::quit(); QApplication::quit();
*handled = true; *handled = true;