diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c1810f..0ce868b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ add_qtc_plugin(QodeAssist widgets/ProgressWidget.hpp widgets/ProgressWidget.cpp widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp + widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp QuickRefactorHandler.hpp QuickRefactorHandler.cpp ) diff --git a/QodeAssist.qrc b/QodeAssist.qrc index 35c2108..fe0d593 100644 --- a/QodeAssist.qrc +++ b/QodeAssist.qrc @@ -2,5 +2,11 @@ resources/images/qoderassist-icon@2x.png resources/images/qoderassist-icon.png + resources/images/repeat-last-instruct-icon@2x.png + resources/images/repeat-last-instruct-icon.png + resources/images/improve-current-code-icon@2x.png + resources/images/improve-current-code-icon.png + resources/images/suggest-new-icon.png + resources/images/suggest-new-icon@2x.png diff --git a/QodeAssistClient.cpp b/QodeAssistClient.cpp index a5e2422..bcb1110 100644 --- a/QodeAssistClient.cpp +++ b/QodeAssistClient.cpp @@ -352,7 +352,6 @@ void QodeAssistClient::cleanupConnections() void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) { - m_progressHandler.hideProgress(); if (!result.success) { LOG_MESSAGE(QString("Refactoring failed: %1").arg(result.errorMessage)); return; @@ -377,5 +376,6 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result) cursor.insertText(result.newText); cursor.endEditBlock(); + m_progressHandler.hideProgress(); } } // namespace QodeAssist diff --git a/qodeassist.cpp b/qodeassist.cpp index 8b6da81..8f459f3 100644 --- a/qodeassist.cpp +++ b/qodeassist.cpp @@ -56,6 +56,7 @@ #include "settings/ProjectSettingsPanel.hpp" #include "settings/SettingsConstants.hpp" #include "templates/Templates.hpp" +#include "widgets/QuickRefactorDialog.hpp" #include #include #include @@ -148,17 +149,17 @@ public: quickRefactorAction.setIcon(QCODEASSIST_ICON.icon()); quickRefactorAction.addOnTriggered(this, [this] { if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) { - bool ok; if (m_qodeAssistClient && m_qodeAssistClient->reachable()) { - QString instructions = QInputDialog::getText( - Core::ICore::dialogParent(), - Tr::tr("Quick Refactor"), - Tr::tr("Enter refactoring instructions:"), - QLineEdit::Normal, - QString(), - &ok); - if (ok) - m_qodeAssistClient->requestQuickRefactor(editor, instructions); + QuickRefactorDialog + dialog(Core::ICore::dialogParent(), m_lastRefactorInstructions); + + if (dialog.exec() == QDialog::Accepted) { + QString instructions = dialog.instructions(); + if (!instructions.isEmpty()) { + m_lastRefactorInstructions = instructions; + m_qodeAssistClient->requestQuickRefactor(editor, instructions); + } + } } else { qWarning() << "The QodeAssist is not ready. Please check your connection and " "settings."; @@ -235,6 +236,7 @@ private: QPointer m_navigationPanel; QPointer m_updater; UpdateStatusWidget *m_statusWidget{nullptr}; + QString m_lastRefactorInstructions; }; } // namespace QodeAssist::Internal diff --git a/resources/images/improve-current-code-icon.png b/resources/images/improve-current-code-icon.png new file mode 100644 index 0000000..2108d98 Binary files /dev/null and b/resources/images/improve-current-code-icon.png differ diff --git a/resources/images/improve-current-code-icon@2x.png b/resources/images/improve-current-code-icon@2x.png new file mode 100644 index 0000000..ec9262c Binary files /dev/null and b/resources/images/improve-current-code-icon@2x.png differ diff --git a/resources/images/repeat-last-instruct-icon.png b/resources/images/repeat-last-instruct-icon.png new file mode 100644 index 0000000..7a5e66e Binary files /dev/null and b/resources/images/repeat-last-instruct-icon.png differ diff --git a/resources/images/repeat-last-instruct-icon@2x.png b/resources/images/repeat-last-instruct-icon@2x.png new file mode 100644 index 0000000..32314c0 Binary files /dev/null and b/resources/images/repeat-last-instruct-icon@2x.png differ diff --git a/resources/images/suggest-new-icon.png b/resources/images/suggest-new-icon.png new file mode 100644 index 0000000..297da26 Binary files /dev/null and b/resources/images/suggest-new-icon.png differ diff --git a/resources/images/suggest-new-icon@2x.png b/resources/images/suggest-new-icon@2x.png new file mode 100644 index 0000000..7bd568e Binary files /dev/null and b/resources/images/suggest-new-icon@2x.png differ diff --git a/widgets/QuickRefactorDialog.cpp b/widgets/QuickRefactorDialog.cpp new file mode 100644 index 0000000..e53a6ef --- /dev/null +++ b/widgets/QuickRefactorDialog.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2025 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#include "QuickRefactorDialog.hpp" +#include "QodeAssisttr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace QodeAssist { + +QuickRefactorDialog::QuickRefactorDialog(QWidget *parent, const QString &lastInstructions) + : QDialog(parent) + , m_lastInstructions(lastInstructions) +{ + setWindowTitle(Tr::tr("Quick Refactor")); + setupUi(); + + QTimer::singleShot(0, this, &QuickRefactorDialog::updateDialogSize); + m_textEdit->installEventFilter(this); + updateDialogSize(); +} + +void QuickRefactorDialog::setupUi() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->setSpacing(8); + + QHBoxLayout *actionsLayout = new QHBoxLayout(); + actionsLayout->setSpacing(4); + createActionButtons(); + actionsLayout->addWidget(m_repeatButton); + actionsLayout->addWidget(m_improveButton); + actionsLayout->addWidget(m_alternativeButton); + actionsLayout->addStretch(); + mainLayout->addLayout(actionsLayout); + + m_instructionsLabel = new QLabel(Tr::tr("Enter refactoring instructions:"), this); + mainLayout->addWidget(m_instructionsLabel); + + m_textEdit = new QPlainTextEdit(this); + m_textEdit->setMinimumHeight(100); + m_textEdit->setPlaceholderText(Tr::tr("Type your refactoring instructions here...")); + + connect(m_textEdit, &QPlainTextEdit::textChanged, this, &QuickRefactorDialog::updateDialogSize); + + mainLayout->addWidget(m_textEdit); + + QDialogButtonBox *buttonBox + = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + mainLayout->addWidget(buttonBox); +} + +void QuickRefactorDialog::createActionButtons() +{ + Utils::Icon REPEAT_ICON( + {{":/resources/images/repeat-last-instruct-icon.png", Utils::Theme::IconsBaseColor}}); + Utils::Icon IMPROVE_ICON( + {{":/resources/images/improve-current-code-icon.png", Utils::Theme::IconsBaseColor}}); + Utils::Icon ALTER_ICON( + {{":/resources/images/suggest-new-icon.png", Utils::Theme::IconsBaseColor}}); + m_repeatButton = new QToolButton(this); + m_repeatButton->setIcon(REPEAT_ICON.icon()); + m_repeatButton->setToolTip(Tr::tr("Repeat Last Instructions")); + m_repeatButton->setEnabled(!m_lastInstructions.isEmpty()); + connect(m_repeatButton, &QToolButton::clicked, this, &QuickRefactorDialog::useLastInstructions); + + m_improveButton = new QToolButton(this); + m_improveButton->setIcon(IMPROVE_ICON.icon()); + m_improveButton->setToolTip(Tr::tr("Improve Current Code")); + connect( + m_improveButton, &QToolButton::clicked, this, &QuickRefactorDialog::useImproveCodeTemplate); + + m_alternativeButton = new QToolButton(this); + m_alternativeButton->setIcon(ALTER_ICON.icon()); + m_alternativeButton->setToolTip(Tr::tr("Suggest Alternative Solution")); + connect( + m_alternativeButton, + &QToolButton::clicked, + this, + &QuickRefactorDialog::useAlternativeSolutionTemplate); +} + +QString QuickRefactorDialog::instructions() const +{ + return m_textEdit->toPlainText(); +} + +void QuickRefactorDialog::setInstructions(const QString &instructions) +{ + m_textEdit->setPlainText(instructions); +} + +QuickRefactorDialog::Action QuickRefactorDialog::selectedAction() const +{ + return m_selectedAction; +} + +bool QuickRefactorDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == m_textEdit && event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + if (keyEvent->modifiers() & Qt::ShiftModifier) { + return false; + } + + accept(); + return true; + } + } + return QDialog::eventFilter(watched, event); +} + +void QuickRefactorDialog::useLastInstructions() +{ + if (!m_lastInstructions.isEmpty()) { + m_textEdit->setPlainText(m_lastInstructions); + m_selectedAction = Action::RepeatLast; + } + accept(); +} + +void QuickRefactorDialog::useImproveCodeTemplate() +{ + m_textEdit->setPlainText(Tr::tr( + "Improve the selected code by enhancing readability, efficiency, and maintainability. " + "Follow best practices for C++/Qt and fix any potential issues.")); + m_selectedAction = Action::ImproveCode; + accept(); +} + +void QuickRefactorDialog::useAlternativeSolutionTemplate() +{ + m_textEdit->setPlainText( + Tr::tr("Suggest an alternative implementation approach for the selected code. " + "Provide a different solution that might be cleaner, more efficient, " + "or uses different Qt/C++ patterns or idioms.")); + m_selectedAction = Action::AlternativeSolution; + accept(); +} + +void QuickRefactorDialog::updateDialogSize() +{ + QString text = m_textEdit->toPlainText(); + + QFontMetrics fm(m_textEdit->font()); + + QStringList lines = text.split('\n'); + int lineCount = lines.size(); + + if (lineCount <= 1) { + int singleLineHeight = fm.height() + 10; + m_textEdit->setMinimumHeight(singleLineHeight); + m_textEdit->setMaximumHeight(singleLineHeight); + } else { + m_textEdit->setMaximumHeight(QWIDGETSIZE_MAX); + + int lineHeight = fm.height() + 2; + + int textEditHeight = qMin(qMax(lineCount, 2) * lineHeight, 20 * lineHeight); + m_textEdit->setMinimumHeight(textEditHeight); + } + + int maxWidth = 500; + for (const QString &line : lines) { + int lineWidth = fm.horizontalAdvance(line) + 30; + maxWidth = qMax(maxWidth, qMin(lineWidth, 800)); + } + + QScreen *screen = QApplication::primaryScreen(); + QRect screenGeometry = screen->availableGeometry(); + + int newWidth = qMin(maxWidth + 40, screenGeometry.width() * 3 / 4); + + int newHeight; + if (lineCount <= 1) { + newHeight = 150; + } else { + newHeight = m_textEdit->minimumHeight() + 150; + } + newHeight = qMin(newHeight, screenGeometry.height() * 3 / 4); + + resize(newWidth, newHeight); +} + +} // namespace QodeAssist diff --git a/widgets/QuickRefactorDialog.hpp b/widgets/QuickRefactorDialog.hpp new file mode 100644 index 0000000..0f5b7e8 --- /dev/null +++ b/widgets/QuickRefactorDialog.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include + +class QPlainTextEdit; +class QToolButton; +class QLabel; + +namespace QodeAssist { + +class QuickRefactorDialog : public QDialog +{ + Q_OBJECT + +public: + enum class Action { Custom, RepeatLast, ImproveCode, AlternativeSolution }; + + explicit QuickRefactorDialog( + QWidget *parent = nullptr, const QString &lastInstructions = QString()); + ~QuickRefactorDialog() override = default; + + QString instructions() const; + void setInstructions(const QString &instructions); + + Action selectedAction() const; + + bool eventFilter(QObject *watched, QEvent *event) override; + +private slots: + void useLastInstructions(); + void useImproveCodeTemplate(); + void useAlternativeSolutionTemplate(); + void updateDialogSize(); + +private: + void setupUi(); + void createActionButtons(); + + QPlainTextEdit *m_textEdit; + QToolButton *m_repeatButton; + QToolButton *m_improveButton; + QToolButton *m_alternativeButton; + QLabel *m_instructionsLabel; + + Action m_selectedAction = Action::Custom; + QString m_lastInstructions; +}; + +} // namespace QodeAssist