diff --git a/CMakeLists.txt b/CMakeLists.txt index d9304c7..252930e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ add_subdirectory(external) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb63d79..f8ef0a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,4 +23,9 @@ add_qtc_plugin(QNVim qnvimplugin.h qnvimcore.cpp qnvimcore.h + cmdline.cpp + cmdline.h + automap.h + textediteventfilter.cpp + textediteventfilter.h ) diff --git a/src/automap.h b/src/automap.h new file mode 100644 index 0000000..472239d --- /dev/null +++ b/src/automap.h @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2023 Mikhail Zolotukhin +// SPDX-License-Identifier: MIT +#pragma once + +#include + +#include +#include + +namespace QNVim::Internal { + +class AutoMapBase : public QObject { + Q_OBJECT + protected: + explicit AutoMapBase(QObject *parent = nullptr) : QObject(parent){}; +}; + +template +concept QObjectBasedPointer = + std::is_pointer_v && std::is_base_of_v>; + +/** + * Map, that automatically deletes pairs, when key or value is deleted, + * given that either a key or a value is a pointer to QObject. + */ +template +requires (QObjectBasedPointer || QObjectBasedPointer) +class AutoMap : public AutoMapBase { + public: + explicit AutoMap(QObject *parent = nullptr) : AutoMapBase(parent){}; + + V &at(const K &key) { + return m_map.at(key); + } + + auto begin() { return m_map.begin(); }; + auto end() { return m_map.end(); }; + auto cbegin() const { return m_map.cbegin(); }; + auto cend() const { return m_map.cend(); }; + + auto contains(const K& key) const { + return m_map.contains(key); + } + + auto find(const K &key) { + return m_map.find(key); + } + + auto find(const K &key) const { + return m_map.find(key); + } + + auto insert(const std::pair &v) { + auto result = m_map.insert(v); + + if (!result.second) + return result; + + if constexpr (std::is_base_of_v>) + connect(v.first, &QObject::destroyed, this, [=]() { + m_map.erase(v.first); + }); + + if constexpr (std::is_base_of_v>) + connect(v.second, &QObject::destroyed, this, [=]() { + m_map.erase(v.first); + }); + + return result; + } + + auto insert_or_assign(const K &k, V &&v) { + auto it = m_map.find(k); + + if (it == m_map.end()) { + auto [it, _] = this->insert({k, v}); + return std::make_pair(it, true); + } else { + if constexpr (std::is_base_of_v>) { + it->second->disconnect(this); + connect(v, &QObject::destroyed, this, [=]() { + m_map.erase(k); + }); + } + + it->second = v; + return std::make_pair(it, false); + } + } + + private: + std::unordered_map m_map; +}; + +} // namespace QNVim::Internal diff --git a/src/cmdline.cpp b/src/cmdline.cpp new file mode 100644 index 0000000..b9383fe --- /dev/null +++ b/src/cmdline.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2023 Mikhail Zolotukhin +// SPDX-License-Identifier: MIT +#include "cmdline.h" + +#include "log.h" +#include "qnvimcore.h" +#include "textediteventfilter.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace QNVim::Internal { + +CmdLine::CmdLine(QObject *parent) : QObject(parent) { + // HACK: + // We need the parent of an editor, so that we can inject CmdLine widget below it. + // Parent widget might be absent when EditorManager::editorOpened is emitted. + // Because of that we detect when the parent changes (from nothing to something) + // and only then call our editorOpened callback + connect( + Core::EditorManager::instance(), &Core::EditorManager::editorOpened, this, + [this](Core::IEditor *editor) { + if (!editor) + return; + + auto editorWidget = editor->widget(); + auto eventFilter = new ParentChangedFilter(editorWidget); + editorWidget->installEventFilter(eventFilter); + + connect( + eventFilter, &ParentChangedFilter::parentChanged, this, + [this, editor](QObject *parent) { + this->editorOpened(*editor); + }, + Qt::SingleShotConnection); + }); + + m_core = qobject_cast(parent); +} + +void CmdLine::onCmdLineShow(QStringView content, + int pos, QChar firstc, QStringView prompt, int indent) { + // Save params for other requests for cmdline + m_firstChar = firstc; + m_prompt = prompt.toString(); + m_indent = indent; + + // Hide all cmds, that could possibly be in other splits + for (auto &[_, cmdWidget] : m_uniqueWidgets) + cmdWidget->hide(); + + auto currentCmdWidget = currentWidget(); + + if (!currentCmdWidget) + return; + + QString text = firstc + prompt + QString(indent, ' ') + content; + + currentCmdWidget->setText(text); + currentCmdWidget->setReadOnly(false); + currentCmdWidget->show(); + + currentCmdWidget->focus(); + + // Update cursor position + auto cursor = currentCmdWidget->textCursor(); + auto cursorPositionFromNvim = firstc.isPrint() + prompt.length() + indent + pos; + cursor.setPosition(cursorPositionFromNvim); + currentCmdWidget->setTextCursor(cursor); +} + +void CmdLine::onCmdLineHide() +{ + auto currentCmd = currentWidget(); + + currentCmd->clear(); + currentCmd->hide(); + + // Focus editor, since we are done + auto currentEditor = Core::EditorManager::currentEditor(); + if (currentEditor && currentEditor->widget()) + currentEditor->widget()->setFocus(); +} + +void CmdLine::onCmdLinePos(int pos) +{ + auto currentCmd = currentWidget(); + + // Update cursor position + auto cursor = currentCmd->textCursor(); + auto cursorPositionFromNvim = m_firstChar.isPrint() + m_prompt.length() + m_indent + pos; + cursor.setPosition(cursorPositionFromNvim); + currentCmd->setTextCursor(cursor); +} + +void CmdLine::showMessage(QStringView message) +{ + auto currentCmd = currentWidget(); + + currentCmd->clear(); + currentCmd->setReadOnly(true); + currentCmd->setText(message.toString()); + + currentCmd->show(); +} + +void CmdLine::clear() +{ + auto currentCmd = currentWidget(); + + currentCmd->clear(); +} + +void CmdLine::editorOpened(Core::IEditor &editor) { + qDebug(Main) << "CmdLine::editorOpened" << &editor; + if (!m_widgets.contains(&editor)) { + auto editorWidget = editor.widget(); + auto stackLayout = editorWidget->parentWidget(); + auto editorView = stackLayout->parentWidget(); + + CmdLineWidget* widgetToAdd = nullptr; + + if (auto it = m_uniqueWidgets.find(editorView); it != m_uniqueWidgets.end()) + widgetToAdd = it->second; // We already have a widget for that editor + else + widgetToAdd = new CmdLineWidget(m_core, editorView); + + m_widgets.insert({&editor, widgetToAdd}); + m_uniqueWidgets.insert_or_assign(editorView, std::move(widgetToAdd)); + } +} + +CmdLineWidget *CmdLine::currentWidget() const { + auto currentEditor = Core::EditorManager::currentEditor(); + + if (!currentEditor) + return nullptr; + + auto it = m_widgets.find(currentEditor); + + if (it == m_widgets.cend()) + return nullptr; + + return it->second; +} + +CmdLineWidget::CmdLineWidget(QNVimCore *core, QWidget *parent) : QWidget(parent) { + auto parentLayout = parent->layout(); + parentLayout->addWidget(this); + + auto pLayout = new QHBoxLayout(this); + pLayout->setContentsMargins(0, 0, 0, 0); + + m_pTextWidget = new QPlainTextEdit(this); + m_pTextWidget->document()->setDocumentMargin(0); + m_pTextWidget->setFrameStyle(QFrame::Shape::NoFrame); + m_pTextWidget->setObjectName(QStringLiteral("cmdline")); + m_pTextWidget->setStyleSheet(QStringLiteral("#cmdline { border-top: 1px solid palette(dark) }")); + + auto editorFont = TextEditor::TextEditorSettings::instance()->fontSettings().font(); + m_pTextWidget->setFont(editorFont); + + auto textEditEventFilter = new TextEditEventFilter(core->nvimConnector(), this); + m_pTextWidget->installEventFilter(textEditEventFilter); + + connect(m_pTextWidget->document()->documentLayout(), + &QAbstractTextDocumentLayout::documentSizeChanged, + this, &CmdLineWidget::adjustSize); + + connect(TextEditor::TextEditorSettings::instance(), + &TextEditor::TextEditorSettings::fontSettingsChanged, + this, [this](const TextEditor::FontSettings &settings) { + m_pTextWidget->setFont(settings.font()); + }); + + adjustSize(m_pTextWidget->document()->size()); + // Do not show by default + hide(); + + pLayout->addWidget(m_pTextWidget); +} + +void CmdLineWidget::setText(const QString &text) +{ + m_pTextWidget->setPlainText(text); +} + +QString CmdLineWidget::text() const +{ + return m_pTextWidget->toPlainText(); +} + +void CmdLineWidget::clear() +{ + m_pTextWidget->clear(); +} + +void CmdLineWidget::focus() const +{ + m_pTextWidget->setFocus(); +} + +void CmdLineWidget::setTextCursor(const QTextCursor &cursor) +{ + m_pTextWidget->setTextCursor(cursor); +} + +QTextCursor CmdLineWidget::textCursor() const +{ + return m_pTextWidget->textCursor(); +} + +void CmdLineWidget::setReadOnly(bool value) +{ + m_pTextWidget->setReadOnly(value); +} + +void CmdLineWidget::adjustSize(const QSizeF &newTextDocumentSize) +{ + auto fontHeight = m_pTextWidget->fontMetrics().height(); + auto newHeight = newTextDocumentSize.height() * fontHeight + m_pTextWidget->frameWidth() * 2; + + m_pTextWidget->setMaximumHeight(newHeight); + m_pTextWidget->parentWidget()->setMaximumHeight(newHeight); +} + +bool ParentChangedFilter::eventFilter(QObject *watched, QEvent *event) { + if (event->type() == QEvent::ParentChange) + emit parentChanged(watched->parent()); + + return QObject::eventFilter(watched, event); +} + +} // namespace QNVim::Internal diff --git a/src/cmdline.h b/src/cmdline.h new file mode 100644 index 0000000..fe21462 --- /dev/null +++ b/src/cmdline.h @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2023 Mikhail Zolotukhin +// SPDX-License-Identifier: MIT +#pragma once + +#include + +#include "automap.h" + +QT_BEGIN_NAMESPACE +class QPlainTextEdit; +class QTextCursor; +QT_END_NAMESPACE + +namespace NeovimQt { +class NeovimConnector; +} + +namespace QNVim::Internal { + +class QNVimCore; + +class CmdLineWidget : public QWidget { + Q_OBJECT + public: + explicit CmdLineWidget(QNVimCore* core, QWidget *parent = nullptr); + + void setText(const QString& text); + QString text() const; + void clear(); + + void focus() const; + + void setTextCursor(const QTextCursor& cursor); + QTextCursor textCursor() const; + + void setReadOnly(bool value); + + private: + void adjustSize(const QSizeF &newSize); + + QPlainTextEdit *m_pTextWidget; +}; + +class CmdLine : public QObject { + Q_OBJECT + + public: + explicit CmdLine(QObject *parent = nullptr); + + /** + * @sa https://neovim.io/doc/user/ui.html#ui-cmdline + */ + void onCmdLineShow(QStringView content, int pos, QChar firstc, QStringView prompt, int indent); + void onCmdLineHide(); + void onCmdLinePos(int pos); + + void showMessage(QStringView message); + void clear(); + + private: + void editorOpened(Core::IEditor &editor); + CmdLineWidget* currentWidget() const; + + QNVimCore* m_core {}; + + // ':', '/' or '?' char in the beginning + QChar m_firstChar {}; + + // vim.input() prompt, usually used in Neovim plugins + QString m_prompt {}; + + // How many spaces the content of the cmdline should be indented + int m_indent {}; + + // Stores bounds between editors and CmdLineWidgets. + // The CmdLineWidget we receive depends on + AutoMap m_widgets {}; + + // Stores unique bounds between EditorView (which is like a split in Neovim) + // and CmdLineWidget. EditorView is a private class of QtCreator, therefore + // we use a QWidget pointer here. + AutoMap m_uniqueWidgets {}; +}; + +class ParentChangedFilter : public QObject { + Q_OBJECT + public: + explicit ParentChangedFilter(QObject *parent = nullptr) : QObject(parent) {} + + signals: + void parentChanged(QObject* parent); + + protected: + bool eventFilter(QObject *watched, QEvent *event) override; +}; +} // namespace QNVim::Internal diff --git a/src/qnvimcore.cpp b/src/qnvimcore.cpp index dbf1b11..8000014 100644 --- a/src/qnvimcore.cpp +++ b/src/qnvimcore.cpp @@ -5,6 +5,8 @@ #include "numbers_column.h" #include "log.h" +#include "cmdline.h" +#include "textediteventfilter.h" #include #include @@ -55,19 +57,6 @@ QNVimCore::QNVimCore(QObject *parent) : QObject{parent} { qDebug(Main) << "QNVimCore::constructor"; - mCMDLine = new QPlainTextEdit; - Core::StatusBarManager::addStatusBarWidget(mCMDLine, Core::StatusBarManager::First); - mCMDLine->document()->setDocumentMargin(0); - mCMDLine->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - mCMDLine->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - mCMDLine->setLineWrapMode(QPlainTextEdit::NoWrap); - mCMDLine->setMinimumWidth(200); - mCMDLine->setFocusPolicy(Qt::StrongFocus); - mCMDLine->installEventFilter(this); - mCMDLine->setFont(TextEditor::TextEditorSettings::instance()->fontSettings().font()); - - qobject_cast(mCMDLine->parentWidget()->children()[2])->hide(); - saveCursorFlashTime(QApplication::cursorFlashTime()); connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose, @@ -148,13 +137,12 @@ autocmd VimEnter * let $MYQVIMRC=substitute($MYVIMRC, 'init.vim$', 'qnvim.vim', mNVim->api2()->nvim_subscribe("Gui"); mNVim->api2()->nvim_subscribe("api-buffer-updates"); }); + + m_cmdLine = std::make_unique(this); } QNVimCore::~QNVimCore() { - qobject_cast(mCMDLine->parentWidget()->children()[2])->show(); - mCMDLine->deleteLater(); - disconnect(QApplication::styleHints(), &QStyleHints::cursorFlashTimeChanged, this, &QNVimCore::saveCursorFlashTime); QApplication::setCursorFlashTime(mSavedCursorFlashTime); @@ -184,7 +172,6 @@ QNVimCore::~QNVimCore() auto textEditor = qobject_cast(widget); textEditor->setCursorWidth(1); - widget->removeEventFilter(this); mEditors.remove(key); } mBuffers.clear(); @@ -193,9 +180,11 @@ QNVimCore::~QNVimCore() if (mNVim) mNVim->deleteLater(); +} - if (mCMDLine) - Core::StatusBarManager::destroyStatusBarWidget(mCMDLine); +NeovimQt::NeovimConnector *QNVimCore::nvimConnector() +{ + return mNVim; } QString QNVimCore::filename(Core::IEditor *editor) const { @@ -273,16 +262,16 @@ void QNVimCore::syncSelectionToVim(Core::IEditor *editor) { if (mtc.hasMultipleCursors()) { auto mainCursor = mtc.mainCursor(); - // We should always use main cursor pos here, - // because it is the cursor user controls with hjkl + // We should always use main cursor pos here, + // because it is the cursor user controls with hjkl auto nvimPos = mainCursor.position(); - // NOTE: Theoretically, it is not always the case - // that the main cursor is at the ends of mtc array, - // but for creating our own block selections it works, - // because we create cursors one after another, where - // main cursor is at the end or in the beginning. - // @see syncCursorFromVim + // NOTE: Theoretically, it is not always the case + // that the main cursor is at the ends of mtc array, + // but for creating our own block selections it works, + // because we create cursors one after another, where + // main cursor is at the end or in the beginning. + // @see syncCursorFromVim auto lastCursor = mainCursor == *mtc.begin() ? *(mtc.end() - 1) : *mtc.begin(); auto nvimAnchor = lastCursor.anchor(); @@ -605,38 +594,7 @@ void QNVimCore::saveCursorFlashTime(int cursorFlashTime) { this, &QNVimCore::saveCursorFlashTime); } -bool QNVimCore::eventFilter(QObject *object, QEvent *event) { - /* if (qobject_cast(object)) */ - if (qobject_cast(object) || - qobject_cast(object)) { - if (event->type() == QEvent::Resize) { - QTimer::singleShot(100, this, [=]() { fixSize(); }); - return false; - } - } - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - QString key = NeovimQt::Input::convertKey(*keyEvent); - mNVim->api2()->nvim_input(key.toUtf8()); - return true; - } else if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent *keyEvent = static_cast(event); - QString key = NeovimQt::Input::convertKey(*keyEvent); - if (keyEvent->key() == Qt::Key_Escape) { - mNVim->api2()->nvim_input(key.toUtf8()); - } else { - keyEvent->accept(); - } - return true; - } - return false; -} - void QNVimCore::editorOpened(Core::IEditor *editor) { - if (!mEnabled) - return; - if (!editor) return; @@ -727,9 +685,12 @@ void QNVimCore::editorOpened(Core::IEditor *editor) { mNumbersColumn->setEditor(textEditor); widget->setAttribute(Qt::WA_KeyCompression, false); - widget->installEventFilter(this); - QTimer::singleShot(100, this, [=]() { fixSize(editor); }); + auto eventFilter = new TextEditEventFilter(mNVim, this); + widget->installEventFilter(eventFilter); + connect(eventFilter, &TextEditEventFilter::resizeNeeded, this, [this, editor]() { + QTimer::singleShot(100, this, [=]() { fixSize(editor); }); + }); } void QNVimCore::editorAboutToClose(Core::IEditor *editor) { @@ -959,38 +920,38 @@ void QNVimCore::redraw(const QVariantList &args) { mSpecialColor = QRgb(val); } } else if (command == "cmdline_show") { - mCMDLineVisible = true; - QVariantList contentList = args[0].toList(); - mCMDLineContent.clear(); + auto content = QByteArray(); + for (auto &contentItem : args[0].toList()) + content += contentItem.toList()[1].toByteArray(); - for (const auto& contentItem : contentList) - mCMDLineContent += QString::fromUtf8(contentItem.toList()[1].toByteArray()); + auto pos = args[1].toInt(); + auto firstc = args[2].toString()[0]; + auto prompt = QString::fromUtf8(args[3].toByteArray()); + auto indent = args[4].toInt(); - mCMDLinePos = args[1].toInt(); - mCMDLineFirstc = args[2].toString()[0]; - mCMDLinePrompt = QString::fromUtf8(args[3].toByteArray()); - mCMDLineIndent = args[4].toInt(); + m_cmdLine->onCmdLineShow(QString::fromUtf8(content), pos, firstc, prompt, indent); } else if (command == "cmdline_pos") { - mCMDLinePos = args[0].toInt(); + m_cmdLine->onCmdLinePos(args[0].toInt()); } else if (command == "cmdline_hide") { - mCMDLineVisible = false; + m_cmdLine->onCmdLineHide(); } else if (command == "msg_show") { - QVariantList contentList = args[1].toList(); - mMessageLineDisplay.clear(); - for (const auto& contentItem : contentList) - mMessageLineDisplay += QString::fromUtf8(contentItem.toList()[1].toByteArray()); + auto contentList = args[1].toList(); + + auto message = QString(); + for (auto &item : contentList) + message += QString::fromUtf8(item.toList()[1].toByteArray()); + + m_cmdLine->showMessage(message); } else if (command == "msg_clear") { - mMessageLineDisplay.clear(); + m_cmdLine->clear(); } else if (command == "msg_history_show") { QVariantList entries = args[1].toList(); - mMessageLineDisplay.clear(); - for (const auto& entry : entries) { - QVariantList contentList = entry.toList()[1].toList(); + auto message = QString(); + for (auto &item : entries) + message += QString::fromUtf8(item.toList()[1].toByteArray()); - for (const auto& contentItem : contentList) - mMessageLineDisplay += QString::fromUtf8(contentItem.toList()[1].toByteArray()) + '\n'; - } + m_cmdLine->showMessage(message); } } @@ -998,64 +959,6 @@ void QNVimCore::redraw(const QVariantList &args) { syncFromVim(); updateCursorSize(); - - QFontMetrics commandLineFontMetric(mCMDLine->font()); - if (mCMDLineVisible) { - QString text = mCMDLineFirstc + mCMDLinePrompt + QString(mCMDLineIndent, ' ') + mCMDLineContent; - - if (mCMDLine->toPlainText() != text) - mCMDLine->setPlainText(text); - - static const auto endLineRegExp = QRegularExpression("[\n\r]"); - - const auto height = (text.count(endLineRegExp) + 1) * commandLineFontMetric.height(); - auto width = 0; - - const auto lines = text.split(endLineRegExp); - for (const auto& line : lines) { - width += commandLineFontMetric.horizontalAdvance(line); - } - - if (mCMDLine->minimumWidth() != qMax(200, qMin(width + 10, 400))) - mCMDLine->setMinimumWidth(qMax(200, qMin(width + 10, 400))); - - if (mCMDLine->minimumHeight() != qMax(25, qMin(height + 4, 400))) { - mCMDLine->setMinimumHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->parentWidget()->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - } - - if (!mCMDLine->hasFocus()) - mCMDLine->setFocus(); - - QTextCursor cursor = mCMDLine->textCursor(); - if (cursor.position() != (QString(mCMDLineFirstc + mCMDLinePrompt).length() + mCMDLineIndent + mCMDLinePos)) { - cursor.setPosition(QString(mCMDLineFirstc + mCMDLinePrompt).length() + mCMDLineIndent + mCMDLinePos); - mCMDLine->setTextCursor(cursor); - } - - if (mUIMode == "cmdline_normal") { - if (mCMDLine->cursorWidth() != 1) - mCMDLine->setCursorWidth(1); - } else if (mUIMode == "cmdline_insert") { - if (mCMDLine->cursorWidth() != 11) - mCMDLine->setCursorWidth(11); - } - } else { - mCMDLine->setPlainText(mMessageLineDisplay); - - if (mCMDLine->hasFocus()) - textEditor->setFocus(); - - auto height = commandLineFontMetric.height(); - mCMDLine->setMinimumHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - mCMDLine->parentWidget()->parentWidget()->parentWidget()->setFixedHeight(qMax(25, qMin(height + 4, 400))); - } - - mCMDLine->setToolTip(mCMDLine->toPlainText()); } void QNVimCore::updateCursorSize() { diff --git a/src/qnvimcore.h b/src/qnvimcore.h index a2905d3..05306ae 100644 --- a/src/qnvimcore.h +++ b/src/qnvimcore.h @@ -28,6 +28,7 @@ namespace QNVim { namespace Internal { class NumbersColumn; +class CmdLine; /** * Encapsulates plugin's behavior with an assumption, that it is enabled. @@ -38,7 +39,7 @@ class QNVimCore : public QObject { explicit QNVimCore(QObject *parent = nullptr); virtual ~QNVimCore(); - bool eventFilter(QObject *object, QEvent *event) override; + NeovimQt::NeovimConnector *nvimConnector(); protected: QString filename(Core::IEditor * = nullptr) const; @@ -66,9 +67,6 @@ class QNVimCore : public QObject { void redraw(const QVariantList &); void updateCursorSize(); - bool mEnabled = true; - - QPlainTextEdit *mCMDLine = nullptr; NumbersColumn *mNumbersColumn = nullptr; NeovimQt::NeovimConnector *mNVim = nullptr; unsigned mVimChanges = 0; @@ -90,15 +88,6 @@ class QNVimCore : public QObject { bool mRelativeNumber = true; bool mWrap = false; - bool mCMDLineVisible = false; - QString mCMDLineContent; - QString mCMDLineDisplay; - QString mMessageLineDisplay; - int mCMDLinePos; - QChar mCMDLineFirstc; - QString mCMDLinePrompt; - int mCMDLineIndent; - QByteArray mUIMode = "normal"; QByteArray mMode = "n"; QPoint mCursor; @@ -109,7 +98,7 @@ class QNVimCore : public QObject { int mSavedCursorFlashTime = 0; - signals: + std::unique_ptr m_cmdLine; }; } // namespace Internal diff --git a/src/qnvimplugin.h b/src/qnvimplugin.h index bb6c0a3..5b76bb7 100644 --- a/src/qnvimplugin.h +++ b/src/qnvimplugin.h @@ -11,6 +11,7 @@ namespace Internal { class QNVimCore; class NumbersColumn; +class CmdLine; class QNVimPlugin : public ExtensionSystem::IPlugin { Q_OBJECT diff --git a/src/textediteventfilter.cpp b/src/textediteventfilter.cpp new file mode 100644 index 0000000..ba49267 --- /dev/null +++ b/src/textediteventfilter.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Mikhail Zolotukhin +// SPDX-License-Identifier: MIT +#include "textediteventfilter.h" + +#include +#include + +#include +#include + +#include + +namespace QNVim::Internal { + +TextEditEventFilter::TextEditEventFilter(NeovimQt::NeovimConnector *nvim, QObject *parent) + : QObject(parent), m_nvim(nvim) { +} + +bool TextEditEventFilter::eventFilter(QObject *watched, QEvent *event) +{ + if (qobject_cast(watched) || + qobject_cast(watched)) { + if (event->type() == QEvent::Resize) { + emit resizeNeeded(); + return false; + } + } + + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + QString key = NeovimQt::Input::convertKey(*keyEvent); + m_nvim->api6()->nvim_input(key.toUtf8()); + return true; + } else if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent *keyEvent = static_cast(event); + QString key = NeovimQt::Input::convertKey(*keyEvent); + if (keyEvent->key() == Qt::Key_Escape) { + m_nvim->api6()->nvim_input(key.toUtf8()); + } else { + keyEvent->accept(); + } + return true; + } + return false; +} + +} // namespace QNVim::Internal diff --git a/src/textediteventfilter.h b/src/textediteventfilter.h new file mode 100644 index 0000000..853ed86 --- /dev/null +++ b/src/textediteventfilter.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Mikhail Zolotukhin +// SPDX-License-Identifier: MIT +#pragma once + +#include + +namespace NeovimQt { +class NeovimConnector; +} + +namespace QNVim::Internal { + +class TextEditEventFilter : public QObject { + Q_OBJECT + public: + explicit TextEditEventFilter(NeovimQt::NeovimConnector *nvim, QObject *parent = nullptr); + + bool eventFilter(QObject *watched, QEvent *event) override; + signals: + void resizeNeeded(); + + private: + NeovimQt::NeovimConnector *m_nvim; +}; + +} // namespace QNVim::Internal