Compare commits

...

8 Commits

23 changed files with 604 additions and 16 deletions

View File

@ -13,7 +13,7 @@
"Linux"
],
"license": "GPLv3",
"version": "0.5.7",
"version": "0.5.8",
"status": "draft",
"is_pack": false,
"released_at": null,
@ -50,8 +50,13 @@
},
{
"version": "0.5.7",
"is_latest": true,
"is_latest": false,
"released_at": "2025-04-14T01:00:00Z"
},
{
"version": "0.5.8",
"is_latest": true,
"released_at": "2025-04-17T10:00:00Z"
}
],
"icon": "https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d",

View File

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

View File

@ -289,6 +289,11 @@ LLMCore::ContextData LLMClientInterface::prepareContext(
return reader.prepareContext(lineNumber, cursorPosition, m_completeSettings);
}
Context::ContextManager *LLMClientInterface::contextManager() const
{
return m_contextManager;
}
void LLMClientInterface::sendCompletionToClient(
const QString &completion, const QJsonObject &request, bool isComplete)
{

View File

@ -62,6 +62,8 @@ public:
// exposed for tests
void sendData(const QByteArray &data) override;
Context::ContextManager *contextManager() const;
protected:
void startImpl() override;

View File

@ -1,7 +1,7 @@
{
"Id" : "qodeassist",
"Name" : "QodeAssist",
"Version" : "0.5.7",
"Version" : "0.5.8",
"Vendor" : "Petr Mironychev",
"VendorId" : "petrmironychev",
"Copyright" : "(C) ${IDE_COPYRIGHT_YEAR} Petr Mironychev, (C) ${IDE_COPYRIGHT_YEAR} The Qt Company Ltd",

View File

@ -2,5 +2,11 @@
<qresource prefix="/">
<file>resources/images/qoderassist-icon@2x.png</file>
<file>resources/images/qoderassist-icon.png</file>
<file>resources/images/repeat-last-instruct-icon@2x.png</file>
<file>resources/images/repeat-last-instruct-icon.png</file>
<file>resources/images/improve-current-code-icon@2x.png</file>
<file>resources/images/improve-current-code-icon.png</file>
<file>resources/images/suggest-new-icon.png</file>
<file>resources/images/suggest-new-icon@2x.png</file>
</qresource>
</RCC>

View File

@ -49,6 +49,7 @@ namespace QodeAssist {
QodeAssistClient::QodeAssistClient(LLMClientInterface *clientInterface)
: LanguageClient::Client(clientInterface)
, m_llmClient(clientInterface)
, m_recentCharCount(0)
{
setName("QodeAssist");
@ -73,6 +74,14 @@ void QodeAssistClient::openDocument(TextEditor::TextDocument *document)
if (!isEnabled(project))
return;
if (m_llmClient->contextManager()
->ignoreManager()
->shouldIgnore(document->filePath().toUrlishString(), project)) {
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
.arg(document->filePath().toUrlishString()));
return;
}
Client::openDocument(document);
connect(
document,
@ -152,6 +161,14 @@ void QodeAssistClient::requestCompletions(TextEditor::TextEditorWidget *editor)
if (!isEnabled(project))
return;
if (m_llmClient->contextManager()
->ignoreManager()
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
.arg(editor->textDocument()->filePath().toUrlishString()));
return;
}
MultiTextCursor cursor = editor->multiTextCursor();
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
return;
@ -181,6 +198,14 @@ void QodeAssistClient::requestQuickRefactor(
if (!isEnabled(project))
return;
if (m_llmClient->contextManager()
->ignoreManager()
->shouldIgnore(editor->textDocument()->filePath().toUrlishString(), project)) {
LOG_MESSAGE(QString("Ignoring file due to .qodeassistignore: %1")
.arg(editor->textDocument()->filePath().toUrlishString()));
return;
}
if (!m_refactorHandler) {
m_refactorHandler = new QuickRefactorHandler(this);
connect(
@ -327,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;
@ -352,5 +376,6 @@ void QodeAssistClient::handleRefactoringResult(const RefactorResult &result)
cursor.insertText(result.newText);
cursor.endEditBlock();
m_progressHandler.hideProgress();
}
} // namespace QodeAssist

View File

@ -69,6 +69,7 @@ private:
CompletionProgressHandler m_progressHandler;
EditorChatButtonHandler m_chatButtonHandler;
QuickRefactorHandler *m_refactorHandler{nullptr};
LLMClientInterface *m_llmClient;
};
} // namespace QodeAssist

View File

@ -2,7 +2,7 @@
[![Build plugin](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml/badge.svg?branch=main)](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Palm1r/QodeAssist/total?color=41%2C173%2C71)
![GitHub Tag](https://img.shields.io/github/v/tag/Palm1r/QodeAssist)
![Static Badge](https://img.shields.io/badge/QtCreator-16.0.0-brightgreen)
![Static Badge](https://img.shields.io/badge/QtCreator-16.0.1-brightgreen)
[![](https://dcbadge.limes.pink/api/server/BGMkUsXUgf?style=flat)](https://discord.gg/BGMkUsXUgf)
![qodeassist-icon](https://github.com/user-attachments/assets/dc336712-83cb-440d-8761-8d0a31de898d) QodeAssist is an AI-powered coding assistant plugin for Qt Creator. It provides intelligent code completion and suggestions for C++ and QML, leveraging large language models through local providers like Ollama. Enhance your coding productivity with context-aware AI assistance directly in your Qt development environment.
@ -35,6 +35,7 @@
- AI-powered code completion
- Sharing IDE opened files with model context (disabled by default, need enable in settings)
- Quick refactor code via fast chat command and opened files
- Chat functionality:
- Side and Bottom panels
- Chat history autosave and restore
@ -61,6 +62,11 @@ Join our Discord Community: Have questions or want to discuss QodeAssist? Join o
<img src="https://github.com/user-attachments/assets/255a52f1-5cc0-4ca3-b05c-c4cf9cdbe25a" width="600" alt="QodeAssistPreview">
</details>
<details>
<summary>Quick refactor in code: (click to expand)</summary>
<img src="https://github.com/user-attachments/assets/4a3a6dab-13e2-46ba-bf75-09f9362f5a6e" width="600" alt="QodeAssistPreview">
</details>
<details>
<summary>Multiline Code completion: (click to expand)</summary>
<img src="https://github.com/user-attachments/assets/c18dfbd2-8c54-4a7b-90d1-66e3bb51adb0" width="600" alt="QodeAssistPreview">
@ -230,7 +236,8 @@ Linked files provide persistent context throughout the conversation:
## QtCreator Version Compatibility
- QtCreator 16.0.0 - 0.5.2 - 0.5.x
- QtCreator 16.0.1 - 0.5.7 - 0.x.x
- QtCreator 16.0.0 - 0.5.2 - 0.5.6
- QtCreator 15.0.1 - 0.4.8 - 0.5.1
- QtCreator 15.0.0 - 0.4.0 - 0.4.7
- QtCreator 14.0.2 - 0.2.3 - 0.3.x
@ -253,6 +260,10 @@ Linked files provide persistent context throughout the conversation:
- on Linux with KDE Plasma: Ctrl + Alt + Q
- To insert the full suggestion, you can use the TAB key
- To insert word of suggistion, you can use Alt + Right Arrow for Win/Lin, or Option + Right Arrow for Mac
- To call Quick Refactor dialog, select some code or place cursor and press
- on Mac: Option + Command + R
- on Windows: Ctrl + Alt + R
- on Linux with KDE Plasma: Ctrl + Alt + R
## Troubleshooting

View File

@ -8,6 +8,7 @@ add_library(Context STATIC
TokenUtils.hpp TokenUtils.cpp
ProgrammingLanguage.hpp ProgrammingLanguage.cpp
IContextManager.hpp
IgnoreManager.hpp IgnoreManager.cpp
)
target_link_libraries(Context

View File

@ -27,6 +27,7 @@
#include "settings/GeneralSettings.hpp"
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <texteditor/textdocument.h>
@ -36,6 +37,7 @@ namespace QodeAssist::Context {
ContextManager::ContextManager(QObject *parent)
: QObject(parent)
, m_ignoreManager(new IgnoreManager(this))
{}
QString ContextManager::readFile(const QString &filePath) const
@ -52,6 +54,13 @@ QList<ContentFile> ContextManager::getContentFiles(const QStringList &filePaths)
{
QList<ContentFile> files;
for (const QString &path : filePaths) {
auto project = ProjectExplorer::ProjectManager::projectForFile(
Utils::FilePath::fromString(path));
if (project && m_ignoreManager->shouldIgnore(path, project)) {
LOG_MESSAGE(QString("Ignoring file in context due to .qodeassistignore: %1").arg(path));
continue;
}
ContentFile contentFile = createContentFile(path);
files.append(contentFile);
}
@ -121,6 +130,14 @@ QList<QPair<QString, QString>> ContextManager::openedFiles(const QStringList exc
continue;
auto filePath = textDocument->filePath().toUrlishString();
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
LOG_MESSAGE(
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
continue;
}
if (!excludeFiles.contains(filePath)) {
files.append({filePath, textDocument->plainText()});
}
@ -144,6 +161,13 @@ QString ContextManager::openedFilesContext(const QStringList excludeFiles)
if (excludeFiles.contains(filePath))
continue;
auto project = ProjectExplorer::ProjectManager::projectForFile(textDocument->filePath());
if (project && m_ignoreManager->shouldIgnore(filePath, project)) {
LOG_MESSAGE(
QString("Ignoring file in context due to .qodeassistignore: %1").arg(filePath));
continue;
}
context += QString("File: %1\n").arg(filePath);
context += textDocument->plainText();
@ -153,4 +177,9 @@ QString ContextManager::openedFilesContext(const QStringList excludeFiles)
return context;
}
IgnoreManager *ContextManager::ignoreManager() const
{
return m_ignoreManager;
}
} // namespace QodeAssist::Context

View File

@ -24,6 +24,7 @@
#include "ContentFile.hpp"
#include "IContextManager.hpp"
#include "IgnoreManager.hpp"
#include "ProgrammingLanguage.hpp"
namespace ProjectExplorer {
@ -49,6 +50,11 @@ public:
bool isSpecifyCompletion(const DocumentInfo &documentInfo) const override;
QList<QPair<QString, QString>> openedFiles(const QStringList excludeFiles = QStringList{}) const;
QString openedFilesContext(const QStringList excludeFiles = QStringList{});
IgnoreManager *ignoreManager() const;
private:
IgnoreManager *m_ignoreManager;
};
} // namespace QodeAssist::Context

154
context/IgnoreManager.cpp Normal file
View File

@ -0,0 +1,154 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "IgnoreManager.hpp"
#include <projectexplorer/project.h>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>
#include "logger/Logger.hpp"
namespace QodeAssist::Context {
IgnoreManager::IgnoreManager(QObject *parent)
: QObject(parent)
{
connect(&m_fileWatcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path) {
for (auto it = m_projectIgnorePatterns.begin(); it != m_projectIgnorePatterns.end(); ++it) {
if (ignoreFilePath(it.key()) == path) {
reloadIgnorePatterns(it.key());
break;
}
}
});
}
IgnoreManager::~IgnoreManager() = default;
bool IgnoreManager::shouldIgnore(const QString &filePath, ProjectExplorer::Project *project) const
{
if (!project)
return false;
if (!m_projectIgnorePatterns.contains(project)) {
const_cast<IgnoreManager *>(this)->reloadIgnorePatterns(project);
}
const QStringList &patterns = m_projectIgnorePatterns[project];
if (patterns.isEmpty())
return false;
QDir projectDir(project->projectDirectory().toUrlishString());
QString relativePath = projectDir.relativeFilePath(filePath);
return matchesIgnorePatterns(relativePath, patterns);
}
void IgnoreManager::reloadIgnorePatterns(ProjectExplorer::Project *project)
{
if (!project)
return;
QStringList patterns = loadIgnorePatterns(project);
m_projectIgnorePatterns[project] = patterns;
QString ignoreFile = ignoreFilePath(project);
if (QFileInfo::exists(ignoreFile)) {
if (m_fileWatcher.files().contains(ignoreFile))
m_fileWatcher.removePath(ignoreFile);
m_fileWatcher.addPath(ignoreFile);
if (!m_projectConnections.contains(project)) {
auto connection = connect(project, &QObject::destroyed, this, [this, project]() {
m_projectIgnorePatterns.remove(project);
m_projectConnections.remove(project);
});
m_projectConnections[project] = connection;
}
}
}
QStringList IgnoreManager::loadIgnorePatterns(ProjectExplorer::Project *project)
{
QStringList patterns;
if (!project)
return patterns;
QString path = ignoreFilePath(project);
QFile file(path);
if (!file.exists())
return patterns;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
LOG_MESSAGE(QString("Could not open .qodeassistignore file: %1").arg(path));
return patterns;
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (!line.isEmpty() && !line.startsWith('#'))
patterns << line;
}
return patterns;
}
bool IgnoreManager::matchesIgnorePatterns(const QString &path, const QStringList &patterns) const
{
for (const QString &pattern : patterns) {
QString regexPattern = QRegularExpression::escape(pattern);
regexPattern.replace("\\*\\*", ".*"); // ** matches any characters including /
regexPattern.replace("\\*", "[^/]*"); // * matches any characters except /
regexPattern.replace("\\?", "."); // ? matches any single character
regexPattern = QString("^%1$").arg(regexPattern);
QRegularExpression regex(regexPattern);
if (regex.match(path).hasMatch())
return true;
}
return false;
}
QString IgnoreManager::ignoreFilePath(ProjectExplorer::Project *project) const
{
if (!project) {
return QString();
}
QString path = project->projectDirectory().toUrlishString() + "/.qodeassist/qodeassistignore";
QFileInfo fileInfo(path);
if (!fileInfo.exists() || !fileInfo.isFile()) {
LOG_MESSAGE(QString("File .qodeassistignore not found at path: %1").arg(path));
}
return path;
}
} // namespace QodeAssist::Context

54
context/IgnoreManager.hpp Normal file
View File

@ -0,0 +1,54 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFileSystemWatcher>
#include <QMap>
#include <QObject>
#include <QStringList>
namespace ProjectExplorer {
class Project;
}
namespace QodeAssist::Context {
class IgnoreManager : public QObject
{
Q_OBJECT
public:
explicit IgnoreManager(QObject *parent = nullptr);
~IgnoreManager() override;
bool shouldIgnore(const QString &filePath, ProjectExplorer::Project *project = nullptr) const;
void reloadIgnorePatterns(ProjectExplorer::Project *project);
private:
QStringList loadIgnorePatterns(ProjectExplorer::Project *project);
bool matchesIgnorePatterns(const QString &path, const QStringList &patterns) const;
QString ignoreFilePath(ProjectExplorer::Project *project) const;
mutable QMap<ProjectExplorer::Project *, QStringList> m_projectIgnorePatterns;
QFileSystemWatcher m_fileWatcher;
QMap<ProjectExplorer::Project *, QMetaObject::Connection> m_projectConnections;
};
} // namespace QodeAssist::Context

View File

@ -56,6 +56,7 @@
#include "settings/ProjectSettingsPanel.hpp"
#include "settings/SettingsConstants.hpp"
#include "templates/Templates.hpp"
#include "widgets/QuickRefactorDialog.hpp"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <texteditor/textdocument.h>
@ -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<Chat::NavigationPanel> m_navigationPanel;
QPointer<PluginUpdater> m_updater;
UpdateStatusWidget *m_statusWidget{nullptr};
QString m_lastRefactorInstructions;
};
} // namespace QodeAssist::Internal

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#include "QuickRefactorDialog.hpp"
#include "QodeAssisttr.h"
#include <QApplication>
#include <QDialogButtonBox>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QLabel>
#include <QPlainTextEdit>
#include <QScreen>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
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<QKeyEvent *>(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

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QDialog>
#include <QString>
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