From 31e3d9db7cf9ab28cbf2e164006d29f074005566 Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Sun, 16 Nov 2025 11:24:36 +0100 Subject: [PATCH] feat: Add cancel function for progress indicator --- QodeAssistClient.cpp | 80 ++++++++++++++++++++------- QodeAssistClient.hpp | 3 + QuickRefactorHandler.hpp | 1 + widgets/CompletionProgressHandler.cpp | 10 ++++ widgets/CompletionProgressHandler.hpp | 4 ++ widgets/ProgressWidget.cpp | 62 +++++++++++++++++++-- widgets/ProgressWidget.hpp | 13 +++++ 7 files changed, 148 insertions(+), 25 deletions(-) diff --git a/QodeAssistClient.cpp b/QodeAssistClient.cpp index 2205700..9d59db8 100644 --- a/QodeAssistClient.cpp +++ b/QodeAssistClient.cpp @@ -65,8 +65,7 @@ QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface) setupConnections(); m_typingTimer.start(); - - // Create hover handler for refactoring suggestions + m_refactorHoverHandler = new RefactorSuggestionHoverHandler(); } @@ -83,12 +82,12 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document) return; Client::openDocument(document); - - // Register hover handler for this document + auto editors = TextEditor::BaseTextEditor::textEditorsForDocument(document); for (auto *editor : editors) { if (auto *widget = editor->editorWidget()) { widget->addHoverHandler(m_refactorHoverHandler); + widget->installEventFilter(this); } } connect( @@ -187,11 +186,16 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor) documentVersion(filePath), Position(cursor.mainCursor())}}; if (Settings::codeCompletionSettings().showProgressWidget()) { + // Setup cancel callback before showing progress + m_progressHandler.setCancelCallback([this, editor = QPointer(editor)]() { + if (editor) { + cancelRunningRequest(editor); + } + }); m_progressHandler.showProgress(editor); } request.setResponseCallback([this, editor = QPointer(editor)]( const GetCompletionRequest::Response &response) { - qDebug() << "setResponseCallback"; QTC_ASSERT(editor, return); handleCompletions(response, editor); }); @@ -224,6 +228,13 @@ void QodeAssistClient::requestQuickRefactor( &QodeAssistClient::handleRefactoringResult); } + // Setup cancel callback before showing progress + m_progressHandler.setCancelCallback([this, editor = QPointer(editor)]() { + if (editor && m_refactorHandler) { + m_refactorHandler->cancelRequest(); + m_progressHandler.hideProgress(); + } + }); m_progressHandler.showProgress(editor); m_refactorHandler->sendRefactorRequest(editor, instructions); } @@ -260,14 +271,12 @@ void QodeAssistClient::scheduleRequest(TextEditor::TextEditorWidget *editor) void QodeAssistClient::handleCompletions( const GetCompletionRequest::Response &response, TextEditor::TextEditorWidget *editor) { - qDebug() << "hideProgress"; m_progressHandler.hideProgress(); if (response.error()) { log(*response.error()); - - QString errorMessage = tr("Code completion failed: %1") - .arg(response.error()->message()); + + QString errorMessage = tr("Code completion failed: %1").arg(response.error()->message()); m_errorHandler.showError(editor, errorMessage); return; } @@ -314,7 +323,7 @@ void QodeAssistClient::handleCompletions( Text::Position pos{toTextPos(c.position())}; return TextSuggestion::Data{range, pos, c.text()}; }); - + if (completions.isEmpty()) { LOG_MESSAGE("No valid completions received"); return; @@ -379,11 +388,11 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) QString errorMessage = result.errorMessage.isEmpty() ? tr("Quick refactor failed") : tr("Quick refactor failed: %1").arg(result.errorMessage); - + if (result.editor) { m_errorHandler.showError(result.editor, errorMessage); } - + LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage)); return; } @@ -404,14 +413,14 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) int startPos = range.begin.toPositionInDocument(editorWidget->document()); int endPos = range.end.toPositionInDocument(editorWidget->document()); - + if (startPos != endPos) { QTextCursor startCursor(editorWidget->document()); startCursor.setPosition(startPos); if (startCursor.positionInBlock() > 0) { startCursor.movePosition(QTextCursor::StartOfBlock); } - + QTextCursor endCursor(editorWidget->document()); endCursor.setPosition(endPos); if (endCursor.positionInBlock() > 0) { @@ -420,20 +429,19 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) endCursor.movePosition(QTextCursor::NextCharacter); } } - + Utils::Text::Position expandedBegin = Utils::Text::Position::fromPositionInDocument( editorWidget->document(), startCursor.position()); Utils::Text::Position expandedEnd = Utils::Text::Position::fromPositionInDocument( editorWidget->document(), endCursor.position()); - + range = Utils::Text::Range(expandedBegin, expandedEnd); } TextEditor::TextSuggestion::Data suggestionData{ - Utils::Text::Range{toTextPos(result.insertRange.begin), toTextPos(result.insertRange.end)}, - pos, - result.newText - }; + Utils::Text::Range{toTextPos(result.insertRange.begin), toTextPos(result.insertRange.end)}, + pos, + result.newText}; editorWidget->insertSuggestion( std::make_unique(suggestionData, editorWidget->document())); @@ -453,4 +461,36 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) LOG_MESSAGE("Displaying refactoring suggestion with hover handler"); } +bool QodeAssistClient::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { + auto *keyEvent = static_cast(event); + + if (keyEvent->key() == Qt::Key_Escape) { + auto *editor = qobject_cast(watched); + + if (editor) { + if (m_runningRequests.contains(editor)) { + cancelRunningRequest(editor); + } + + if (m_scheduledRequests.contains(editor)) { + auto *timer = m_scheduledRequests.value(editor); + if (timer && timer->isActive()) { + timer->stop(); + } + } + + if (m_refactorHandler && m_refactorHandler->isProcessing()) { + m_refactorHandler->cancelRequest(); + } + + m_progressHandler.hideProgress(); + } + } + } + + return LanguageClient::Client::eventFilter(watched, event); +} + } // namespace QodeAssist diff --git a/QodeAssistClient.hpp b/QodeAssistClient.hpp index b01f8eb..b1d882a 100644 --- a/QodeAssistClient.hpp +++ b/QodeAssistClient.hpp @@ -53,6 +53,9 @@ public: void requestQuickRefactor( TextEditor::TextEditorWidget *editor, const QString &instructions = QString()); +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + private: void scheduleRequest(TextEditor::TextEditorWidget *editor); void handleCompletions( diff --git a/QuickRefactorHandler.hpp b/QuickRefactorHandler.hpp index d42182f..2029f70 100644 --- a/QuickRefactorHandler.hpp +++ b/QuickRefactorHandler.hpp @@ -52,6 +52,7 @@ public: void sendRefactorRequest(TextEditor::TextEditorWidget *editor, const QString &instructions); void cancelRequest(); + bool isProcessing() const { return m_isRefactoringInProgress; } signals: void refactoringCompleted(const QodeAssist::RefactorResult &result); diff --git a/widgets/CompletionProgressHandler.cpp b/widgets/CompletionProgressHandler.cpp index 2b0da31..769ebf1 100644 --- a/widgets/CompletionProgressHandler.cpp +++ b/widgets/CompletionProgressHandler.cpp @@ -60,6 +60,11 @@ void CompletionProgressHandler::hideProgress() Utils::ToolTip::hideImmediately(); } +void CompletionProgressHandler::setCancelCallback(std::function callback) +{ + m_cancelCallback = callback; +} + void CompletionProgressHandler::identifyMatch( TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report) { @@ -83,6 +88,11 @@ void CompletionProgressHandler::operateTooltip( m_progressWidget = new ProgressWidget(editorWidget); + // Set cancel callback for the widget + if (m_cancelCallback) { + m_progressWidget->setCancelCallback(m_cancelCallback); + } + const QRect cursorRect = editorWidget->cursorRect(editorWidget->textCursor()); QPoint globalPos = editorWidget->viewport()->mapToGlobal(cursorRect.topLeft()); QPoint localPos = editorWidget->mapFromGlobal(globalPos); diff --git a/widgets/CompletionProgressHandler.hpp b/widgets/CompletionProgressHandler.hpp index fb60857..5a39afe 100644 --- a/widgets/CompletionProgressHandler.hpp +++ b/widgets/CompletionProgressHandler.hpp @@ -21,6 +21,7 @@ #include #include +#include namespace QodeAssist { @@ -31,6 +32,8 @@ class CompletionProgressHandler : public TextEditor::BaseHoverHandler public: void showProgress(TextEditor::TextEditorWidget *widget); void hideProgress(); + void setCancelCallback(std::function callback); + bool isProgressVisible() const { return !m_progressWidget.isNull(); } protected: void identifyMatch( @@ -41,6 +44,7 @@ private: QPointer m_widget; QPointer m_progressWidget; QPoint m_iconPosition; + std::function m_cancelCallback; }; } // namespace QodeAssist diff --git a/widgets/ProgressWidget.cpp b/widgets/ProgressWidget.cpp index 2e4b207..2454e45 100644 --- a/widgets/ProgressWidget.cpp +++ b/widgets/ProgressWidget.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2025 Petr Mironychev * * This file is part of QodeAssist. @@ -19,10 +19,15 @@ #include "ProgressWidget.hpp" +#include +#include +#include + namespace QodeAssist { ProgressWidget::ProgressWidget(QWidget *parent) : QWidget(parent) + , m_isHovered(false) { m_dotPosition = 0; m_timer.setInterval(300); @@ -64,6 +69,10 @@ ProgressWidget::ProgressWidget(QWidget *parent) } setFixedSize(40, 40); + setMouseTracking(true); + + QIcon closeIcon = QApplication::style()->standardIcon(QStyle::SP_DockWidgetCloseButton); + m_closePixmap = closeIcon.pixmap(16, 16); } ProgressWidget::~ProgressWidget() @@ -71,6 +80,11 @@ ProgressWidget::~ProgressWidget() m_timer.stop(); } +void ProgressWidget::setCancelCallback(std::function callback) +{ + m_cancelCallback = callback; +} + void ProgressWidget::paintEvent(QPaintEvent *) { QPainter painter(this); @@ -78,10 +92,21 @@ void ProgressWidget::paintEvent(QPaintEvent *) painter.fillRect(rect(), m_backgroundColor); - if (!m_logoPixmap.isNull()) { - QRect logoRect( - (width() - m_logoPixmap.width()) / 2, 5, m_logoPixmap.width(), m_logoPixmap.height()); - painter.drawPixmap(logoRect, m_logoPixmap); + if (m_isHovered) { + if (!m_closePixmap.isNull()) { + int x = ((width() - (m_closePixmap.width() / 2)) / 2); + int y = ((height() - (m_closePixmap.height() / 2)) / 2); + painter.drawPixmap(x, y, m_closePixmap); + } + } else { + if (!m_logoPixmap.isNull()) { + QRect logoRect( + (width() - m_logoPixmap.width()) / 2, + 5, + m_logoPixmap.width(), + m_logoPixmap.height()); + painter.drawPixmap(logoRect, m_logoPixmap); + } } int dotSpacing = 6; @@ -111,4 +136,31 @@ void ProgressWidget::paintEvent(QPaintEvent *) } } +void ProgressWidget::enterEvent(QEnterEvent *event) +{ + Q_UNUSED(event); + m_isHovered = true; + setCursor(Qt::PointingHandCursor); + update(); +} + +void ProgressWidget::leaveEvent(QEvent *event) +{ + Q_UNUSED(event); + m_isHovered = false; + setCursor(Qt::ArrowCursor); + update(); +} + +void ProgressWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && m_isHovered) { + emit cancelRequested(); + if (m_cancelCallback) { + m_cancelCallback(); + } + } + QWidget::mousePressEvent(event); +} + } // namespace QodeAssist diff --git a/widgets/ProgressWidget.hpp b/widgets/ProgressWidget.hpp index 27f0b13..c4380e2 100644 --- a/widgets/ProgressWidget.hpp +++ b/widgets/ProgressWidget.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -30,12 +31,21 @@ namespace QodeAssist { class ProgressWidget : public QWidget { + Q_OBJECT public: ProgressWidget(QWidget *parent = nullptr); ~ProgressWidget(); + void setCancelCallback(std::function callback); + +signals: + void cancelRequested(); + protected: void paintEvent(QPaintEvent *) override; + void enterEvent(QEnterEvent *event) override; + void leaveEvent(QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; private: QTimer m_timer; @@ -43,6 +53,9 @@ private: QColor m_textColor; QColor m_backgroundColor; QPixmap m_logoPixmap; + QPixmap m_closePixmap; + bool m_isHovered; + std::function m_cancelCallback; }; } // namespace QodeAssist