mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-02-08 16:20:12 -05:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 674b1fecde | |||
| b36d01d2c7 | |||
| 615175bea8 | |||
| 7515599acb | |||
| 3652d4d5d9 | |||
| 75677770b2 | |||
| 329a1efd5d | |||
| 27760a3b99 |
9
.github/scripts/plugin.json
vendored
9
.github/scripts/plugin.json
vendored
@ -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",
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -62,6 +62,8 @@ public:
|
||||
// exposed for tests
|
||||
void sendData(const QByteArray &data) override;
|
||||
|
||||
Context::ContextManager *contextManager() const;
|
||||
|
||||
protected:
|
||||
void startImpl() override;
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -69,6 +69,7 @@ private:
|
||||
CompletionProgressHandler m_progressHandler;
|
||||
EditorChatButtonHandler m_chatButtonHandler;
|
||||
QuickRefactorHandler *m_refactorHandler{nullptr};
|
||||
LLMClientInterface *m_llmClient;
|
||||
};
|
||||
|
||||
} // namespace QodeAssist
|
||||
|
||||
15
README.md
15
README.md
@ -2,7 +2,7 @@
|
||||
[](https://github.com/Palm1r/QodeAssist/actions/workflows/build_cmake.yml)
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://discord.gg/BGMkUsXUgf)
|
||||
|
||||
 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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
154
context/IgnoreManager.cpp
Normal 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
54
context/IgnoreManager.hpp
Normal 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
|
||||
@ -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
|
||||
|
||||
BIN
resources/images/improve-current-code-icon.png
Normal file
BIN
resources/images/improve-current-code-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/improve-current-code-icon@2x.png
Normal file
BIN
resources/images/improve-current-code-icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/images/repeat-last-instruct-icon.png
Normal file
BIN
resources/images/repeat-last-instruct-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/images/repeat-last-instruct-icon@2x.png
Normal file
BIN
resources/images/repeat-last-instruct-icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
resources/images/suggest-new-icon.png
Normal file
BIN
resources/images/suggest-new-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 963 B |
BIN
resources/images/suggest-new-icon@2x.png
Normal file
BIN
resources/images/suggest-new-icon@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
217
widgets/QuickRefactorDialog.cpp
Normal file
217
widgets/QuickRefactorDialog.cpp
Normal 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
|
||||
69
widgets/QuickRefactorDialog.hpp
Normal file
69
widgets/QuickRefactorDialog.hpp
Normal 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
|
||||
Reference in New Issue
Block a user