feat: Add cancel function for progress indicator

This commit is contained in:
Petr Mironychev
2025-11-16 11:24:36 +01:00
parent 6f680e3974
commit 31e3d9db7c
7 changed files with 148 additions and 25 deletions

View File

@ -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<TextEditorWidget>(editor)]() {
if (editor) {
cancelRunningRequest(editor);
}
});
m_progressHandler.showProgress(editor);
}
request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(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<TextEditorWidget>(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<RefactorSuggestion>(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<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape) {
auto *editor = qobject_cast<TextEditor::TextEditorWidget *>(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

View File

@ -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(

View File

@ -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);

View File

@ -60,6 +60,11 @@ void CompletionProgressHandler::hideProgress()
Utils::ToolTip::hideImmediately();
}
void CompletionProgressHandler::setCancelCallback(std::function<void()> 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);

View File

@ -21,6 +21,7 @@
#include <texteditor/basehoverhandler.h>
#include <QPointer>
#include <functional>
namespace QodeAssist {
@ -31,6 +32,8 @@ class CompletionProgressHandler : public TextEditor::BaseHoverHandler
public:
void showProgress(TextEditor::TextEditorWidget *widget);
void hideProgress();
void setCancelCallback(std::function<void()> callback);
bool isProgressVisible() const { return !m_progressWidget.isNull(); }
protected:
void identifyMatch(
@ -41,6 +44,7 @@ private:
QPointer<TextEditor::TextEditorWidget> m_widget;
QPointer<ProgressWidget> m_progressWidget;
QPoint m_iconPosition;
std::function<void()> m_cancelCallback;
};
} // namespace QodeAssist

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2025 Petr Mironychev
*
* This file is part of QodeAssist.
@ -19,10 +19,15 @@
#include "ProgressWidget.hpp"
#include <QApplication>
#include <QMouseEvent>
#include <QStyle>
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<void()> 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

View File

@ -22,6 +22,7 @@
#include <QPainter>
#include <QTimer>
#include <QWidget>
#include <functional>
#include <utils/progressindicator.h>
#include <utils/theme/theme.h>
@ -30,12 +31,21 @@ namespace QodeAssist {
class ProgressWidget : public QWidget
{
Q_OBJECT
public:
ProgressWidget(QWidget *parent = nullptr);
~ProgressWidget();
void setCancelCallback(std::function<void()> 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<void()> m_cancelCallback;
};
} // namespace QodeAssist