From 7de4f8b5494dd89d5b6530644865ecbb3f071581 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 9 Oct 2026 11:10:18 +0200 Subject: [PATCH] radial: defer Ollie verb invocation to fix intermittent no-op MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Ollie slices only worked intermittently. onRadialActivated runs from RadialMenu::finishActivated(), which emits activatedId() while the radial popup (Qt::Popup) is still tearing down its grab/focus. The Ollie action methods read MainWindow::activeView() and the editor selection live; during that teardown window activeView() is transiently null (and selection unreadable), so a synchronous invoke silently no-ops — intermittently, depending on timing. The Kate-action path is unaffected because QAction::trigger() does not depend on activeView() being valid at that instant. Defer the Ollie invoke to the next event-loop turn via QTimer::singleShot(0), after the popup has fully closed and the editor is active again. Guard the plugin view with QPointer in case it is unloaded before the deferred call runs. --- src/radial/radialplugin.cpp | 63 +++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/src/radial/radialplugin.cpp b/src/radial/radialplugin.cpp index 2bca716..0f63aae 100644 --- a/src/radial/radialplugin.cpp +++ b/src/radial/radialplugin.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -254,29 +255,45 @@ bool RadialView::triggerOllieVerb(const QString &actionId) const } const QString verb = actionId.mid(QStringLiteral("ollie:").size()); - // The selection-prompt verbs share one method with an argument; the rest are - // zero-argument methods matching the Q_INVOKABLE names on the Ollie view. - if (verb == QLatin1String("fix")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Fix"))); - } else if (verb == QLatin1String("refactor")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Refactor"))); - } else if (verb == QLatin1String("test")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Write unit tests for"))); - } else if (verb == QLatin1String("doc")) { - QMetaObject::invokeMethod(view, "submitSelectionPrompt", - Q_ARG(QString, QStringLiteral("Add documentation comments to"))); - } else if (verb == QLatin1String("ask")) { - QMetaObject::invokeMethod(view, "askAboutFile"); - } else if (verb == QLatin1String("explain")) { - QMetaObject::invokeMethod(view, "explainSelection"); - } else if (verb == QLatin1String("verbatim")) { - QMetaObject::invokeMethod(view, "sendVerbatim"); - } else if (verb == QLatin1String("start")) { - QMetaObject::invokeMethod(view, "startSession"); - } + // Defer the invocation to the next event-loop turn. We are called from + // RadialMenu::finishActivated(), which emits activatedId() while the radial + // (a Qt::Popup) is still tearing down its popup grab/focus. During that + // window MainWindow::activeView() can be transiently null and the editor's + // selection is not reliably readable — the Ollie actions read both live, so + // invoking synchronously here makes them intermittently no-op. Running after + // the popup has fully closed restores the active view and selection first. + // (The Kate-action path does not need this: QAction::trigger() does not + // depend on activeView() being valid at that instant.) Guard the view with + // QPointer in case the plugin is unloaded before the deferred call runs. + QPointer safeView = view; + auto invoke = [safeView, verb]() { + QObject *v = safeView; + if (!v) { + return; + } + if (verb == QLatin1String("fix")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Fix"))); + } else if (verb == QLatin1String("refactor")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Refactor"))); + } else if (verb == QLatin1String("test")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Write unit tests for"))); + } else if (verb == QLatin1String("doc")) { + QMetaObject::invokeMethod(v, "submitSelectionPrompt", + Q_ARG(QString, QStringLiteral("Add documentation comments to"))); + } else if (verb == QLatin1String("ask")) { + QMetaObject::invokeMethod(v, "askAboutFile"); + } else if (verb == QLatin1String("explain")) { + QMetaObject::invokeMethod(v, "explainSelection"); + } else if (verb == QLatin1String("verbatim")) { + QMetaObject::invokeMethod(v, "sendVerbatim"); + } else if (verb == QLatin1String("start")) { + QMetaObject::invokeMethod(v, "startSession"); + } + }; + QTimer::singleShot(0, m_mainWindow->window(), invoke); return true; }