feat: Add custom instructions for quick refactor (#258)

* feat: Add custom commands to quick refactor

* doc: Update for quick refactor feature
This commit is contained in:
Petr Mironychev
2025-11-17 13:53:46 +01:00
committed by GitHub
parent 995597d789
commit 204cffd7d0
10 changed files with 985 additions and 17 deletions

View File

@ -117,6 +117,8 @@ add_qtc_plugin(QodeAssist
widgets/EditorChatButton.hpp widgets/EditorChatButton.cpp
widgets/EditorChatButtonHandler.hpp widgets/EditorChatButtonHandler.cpp
widgets/QuickRefactorDialog.hpp widgets/QuickRefactorDialog.cpp
widgets/CustomInstructionsManager.hpp widgets/CustomInstructionsManager.cpp
widgets/AddCustomInstructionDialog.hpp widgets/AddCustomInstructionDialog.cpp
QuickRefactorHandler.hpp QuickRefactorHandler.cpp
tools/ToolsFactory.hpp tools/ToolsFactory.cpp

View File

@ -30,11 +30,11 @@ QodeAssist enhances Qt Creator with AI-powered coding assistance:
- **Code Completion**: Intelligent, context-aware code suggestions for C++ and QML
- **Chat Assistant**: Multiple interface options (popup window, side panel, bottom panel)
- **Quick Refactoring**: AI-assisted code improvements and alternative suggestions
- **Quick Refactoring**: Inline AI-assisted code improvements directly in editor with custom instructions library
- **File Context**: Attach or link files for better AI understanding
- **Tool Calling**: AI can read project files, search code, and access diagnostics
- **Multiple Providers**: Support for Ollama, Claude, OpenAI, Google AI, Mistral AI, llama.cpp, and more
- **Customizable**: Project-specific rules and extensive model templates
- **Customizable**: Project-specific rules, custom instructions, and extensive model templates
**Join our [Discord Community](https://discord.gg/BGMkUsXUgf)** to get support and connect with other users!
@ -159,9 +159,12 @@ QodeAssist supports multiple LLM providers. Choose your preferred provider and f
- Extended thinking mode (Claude, other providers in plan) - Enable deeper reasoning for complex tasks
### Quick Refactoring
- Fast code refactoring with AI assistance
- Selection-based improvements
- Alternative code suggestions
- Inline code refactoring directly in the editor with AI assistance
- Selection-based improvements with instant code replacement
- Built-in quick actions (repeat, improve, alternative)
- **Custom instructions library** with search and autocomplete
- Create, edit, and manage reusable refactoring templates
- Combine base instructions with specific details
- **[Learn more](docs/quick-refactoring.md)**
### Tools & Function Calling

View File

@ -1,18 +1,276 @@
# Quick Refactoring Feature
Quick Refactoring provides AI-assisted code improvements directly in your editor. Select code, press the hotkey, and get instant refactoring suggestions.
## Overview
Quick Refactoring provides AI-assisted code refactoring with its own dedicated provider and model configuration, allowing you to use different settings than your Chat Assistant. You can use built-in quick actions or create your own custom instructions library.
## Setup
Since this is actually a small chat with redirected output, the main settings of the provider, model and template are taken from the chat settings.
Quick Refactoring has independent configuration separate from Chat Assistant:
## Using
### Provider & Model Configuration
The request to model consist of instructions to model, selection code and cursor position.
Configure provider and model in: `Qt Creator → Preferences → QodeAssist → General Settings`
The default instruction is: "Refactor the code to improve its quality and maintainability." and sending if text field is empty.
Under the **Quick Refactor** section, you can set:
- **Provider**: Choose from Ollama, Claude, OpenAI, Google AI, etc.
- **Model**: Select the specific model for refactoring tasks
- **Template**: Choose the chat template for this provider
- **URL**: Set the API endpoint
- **API Key**: Configure authentication (for cloud providers)
Also there buttons to quick call instractions:
- Repeat latest instruction, will activate after sending first request in QtCreator session
- Improve current selection code
- Suggestion alternative variant of selection code
- Other instructions[TBD]
This allows you to:
- Use a faster/cheaper model for refactoring than for chat
- Use a local model for refactoring and cloud model for chat
- Optimize costs by using different providers for different tasks
### Quick Refactor Settings
Additional refactoring-specific options in: `Qt Creator → Preferences → QodeAssist → Quick Refactor`
Configure:
- **Context Settings**: How much code context to send
- Read full file or N lines before/after selection
- **LLM Parameters**: Temperature, max tokens, top_p, top_k
- **Advanced Options**: Penalties, context window size
- **Features**: Tool calling, extended thinking mode
- **System Prompt**: Customize the base prompt for refactoring
## Using Quick Refactoring
### Basic Usage
1. **Select Code** (or place cursor for line-level refactoring)
2. **Trigger Quick Refactor**: Press `Ctrl+Alt+R` (Windows/Linux) or `⌥⌘R` (macOS)
3. **Choose Action**:
- Use a built-in quick action button
- Select a custom instruction from the dropdown
- Type your own instruction
4. **Get Results**: AI generates refactored code directly replacing your selection
### Quick Action Buttons
The dialog provides three built-in quick actions:
| Button | Description |
|--------|-------------|
| **Repeat Last** | Reuses the last instruction from your session (enabled after first use) |
| **Improve Code** | Enhances readability, efficiency, and maintainability with best practices |
| **Alternative Solution** | Suggests different implementation approaches and patterns |
## Custom Instructions
### Overview
Custom Instructions allow you to create a reusable library of refactoring templates. Instead of typing the same instructions repeatedly, save them once and access them instantly through the searchable dropdown.
**Key Features:**
- **Quick Access**: Search and select instructions by typing
- **Flexible**: Use as-is or add extra details for each use
- **Manageable**: Easy create, edit, and delete interface
- **Persistent**: Instructions saved locally and loaded on startup
- **Accessible**: Direct access to instruction files folder
### Creating Custom Instructions
1. Click the **`+`** button in the Quick Refactor dialog
2. Fill in the form:
- **Name**: Short descriptive title (e.g., "Add Documentation")
- **Instruction Body**: Detailed prompt for the LLM
**Example instruction:**
```
Name: Add Documentation
Body: Add comprehensive documentation to the selected code or code afer cursor following:
Doxygen style. Include parameter descriptions, return value
documentation, and usage examples where applicable.
```
### Using Custom Instructions
#### Method 1: Select and Use
1. Open Quick Refactor dialog (`Ctrl+Alt+R` / `⌥⌘R`)
2. Click the dropdown or start typing instruction name
3. Select instruction (autocomplete will help)
4. Optionally add extra details in the text field below
5. Press OK
#### Method 2: Search by Typing
1. Open Quick Refactor dialog
2. Start typing in the instruction dropdown (e.g., "doc...")
3. Autocomplete shows matching instructions
4. Select with arrow keys or click
5. Add optional details and execute
**Search Features:**
- Case-insensitive search
- Match anywhere in instruction name
- Keyboard navigation (arrow keys, Enter)
- Instant filtering as you type
### Combining Instructions with Additional Details
Custom instructions serve as **base templates** that you can augment with specific requirements:
**Example 1 - Use instruction as-is:**
```
Selected: "Add Documentation"
Additional text: [empty]
→ Sends: "Add comprehensive documentation..."
```
**Example 2 - Add specific requirements:**
```
Selected: "Optimize Performance"
Additional text: "Focus on reducing memory allocations"
→ Sends: "Optimize Performance instructions...
Focus on reducing memory allocations"
```
This approach allows maximum flexibility while maintaining a clean instruction library.
### Managing Custom Instructions
The Quick Refactor dialog provides full CRUD operations:
| Button | Action | Description |
|--------|--------|-------------|
| **+** | Add | Create new custom instruction |
| **✎** | Edit | Modify selected instruction |
| **** | Delete | Remove selected instruction (with confirmation) |
| **📁** | Open Folder | Open instructions directory in file manager |
**Edit/Delete:**
- Select an instruction from dropdown (or type its name)
- Click Edit (✎) or Delete () button
- Confirm changes
<!-- PLACEHOLDER_IMAGE: Screenshot showing instruction management buttons -->
### Storage Location
Custom instructions are stored as JSON files in:
```
~/.config/QtProject/qtcreator/qodeassist/quick_refactor/instructions/
```
**File Naming Format:**
```
Instruction_Name_with_underscores_{unique-uuid}.json
```
**Examples:**
```
Add_Documentation_a7f3c92d-8e4b-4f1a-9c0e-1d2f3a4b5c6d.json
Optimize_Performance_3b8e4f9a-7c2d-4e1b-8f3a-9c1d2e3f4a5b.json
Fix_Code_Style_c5d6e7f8-9a0b-1c2d-3e4f-5a6b7c8d9e0f.json
```
**File Format:**
```json
{
"id": "unique-uuid",
"name": "Add Documentation",
"body": "Add comprehensive documentation...",
"version": "0.1"
}
```
### Backup and Sharing
Since instructions are simple JSON files, you can:
1. **Backup**: Copy the instructions directory
2. **Share**: Share JSON files with team members
3. **Version Control**: Add to your dotfiles repository
4. **Edit Manually**: Modify JSON files directly if needed
Click the **📁** button to quickly open the instructions folder in your file manager.
## Context and Scope
### What Gets Sent to the LLM
The LLM receives:
- **Selected Code** (or current line if no selection)
- **Context**: Surrounding code (configurable amount)
- **File Information**: Language, file path
- **Cursor Position**: Marked with `<cursor>` tag
- **Selection Markers**: `<selection_start>` and `<selection_end>` tags
- **Your Instructions**: Built-in, custom, or typed
- **Project Rules**: If configured (see [Project Rules](project-rules.md))
### Context Configuration
Configure context amount in: `Qt Creator → Preferences → QodeAssist → Quick Refactor`
Options:
- **Read Full File**: Send entire file as context
- **Read File Parts**: Send N lines before/after selection (configurable in "Read Strings Before/After Cursor")
## Advanced Settings
Access all refactoring settings in: `Qt Creator → Preferences → QodeAssist → Quick Refactor`
### Available Options:
**Context Settings:**
- Read full file vs. file parts
- Number of lines before/after cursor
**LLM Parameters:**
- Temperature (creativity/randomness)
- Max tokens (response length)
- Top P (nucleus sampling)
- Top K (vocabulary filtering)
- Presence penalty
- Frequency penalty
**Ollama-specific:**
- Lifetime parameter
- Context window size
**Features:**
- Enable/disable tool calling
- Extended thinking mode (for supported models)
- Thinking budget and max tokens
**Customization:**
- System prompt editing
- Use open files in context (optional)
## Troubleshooting
### Instruction Not Found
- Ensure you've typed the exact name or selected from dropdown
- Check if instruction file exists in instructions directory
- Reload Qt Creator if instructions were added externally
### Poor Results
- Try adding more specific details in the additional text field
- Adjust context settings to provide more/less code
- Use extended thinking mode for complex refactorings
- Check if your model supports the complexity of the task
### Instructions Not Loading
- Verify folder exists: `~/.config/QtProject/qtcreator/qodeassist/quick_refactor/instructions/`
- Check JSON file format validity
- Review Qt Creator logs for parsing errors
- Try restarting Qt Creator
Fully local setup for offline or secure environments.
## Related Documentation
- [Project Rules](project-rules.md) - Project-specific AI behavior customization
- [File Context](file-context.md) - Attaching files to chat context
- [Ignoring Files](ignoring-files.md) - Exclude files from AI context
- [Provider Configuration](../README.md#configuration) - Setting up LLM providers

View File

@ -59,6 +59,7 @@
#include "settings/ProjectSettingsPanel.hpp"
#include "settings/SettingsConstants.hpp"
#include "templates/Templates.hpp"
#include "widgets/CustomInstructionsManager.hpp"
#include "widgets/QuickRefactorDialog.hpp"
#include <ChatView/ChatView.hpp>
#include <coreplugin/actionmanager/actioncontainer.h>
@ -127,6 +128,8 @@ public:
Providers::registerProviders();
Templates::registerTemplates();
CustomInstructionsManager::instance().loadInstructions();
Utils::Icon QCODEASSIST_ICON(
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2024-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 "AddCustomInstructionDialog.hpp"
#include "QodeAssisttr.h"
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QVBoxLayout>
namespace QodeAssist {
AddCustomInstructionDialog::AddCustomInstructionDialog(QWidget *parent)
: QDialog(parent)
{
setWindowTitle(Tr::tr("Add Custom Instruction"));
setupUi();
resize(500, 400);
}
AddCustomInstructionDialog::AddCustomInstructionDialog(const CustomInstruction &instruction, QWidget *parent)
: QDialog(parent)
, m_instruction(instruction)
{
setWindowTitle(Tr::tr("Edit Custom Instruction"));
setupUi();
m_nameEdit->setText(instruction.name);
m_bodyEdit->setPlainText(instruction.body);
resize(500, 400);
}
void AddCustomInstructionDialog::setupUi()
{
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(10);
QFormLayout *formLayout = new QFormLayout();
m_nameEdit = new QLineEdit(this);
m_nameEdit->setPlaceholderText(Tr::tr("Enter instruction name..."));
formLayout->addRow(Tr::tr("Name:"), m_nameEdit);
mainLayout->addLayout(formLayout);
QLabel *bodyLabel = new QLabel(Tr::tr("Instruction Body:"), this);
mainLayout->addWidget(bodyLabel);
m_bodyEdit = new QPlainTextEdit(this);
m_bodyEdit->setPlaceholderText(
Tr::tr("Enter the refactoring instruction that will be sent to the LLM..."));
mainLayout->addWidget(m_bodyEdit);
QDialogButtonBox *buttonBox
= new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, [this]() {
if (m_nameEdit->text().trimmed().isEmpty()) {
QMessageBox::warning(this, Tr::tr("Invalid Input"), Tr::tr("Instruction name cannot be empty."));
return;
}
if (m_bodyEdit->toPlainText().trimmed().isEmpty()) {
QMessageBox::warning(this, Tr::tr("Invalid Input"), Tr::tr("Instruction body cannot be empty."));
return;
}
accept();
});
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mainLayout->addWidget(buttonBox);
}
CustomInstruction AddCustomInstructionDialog::getInstruction() const
{
CustomInstruction instruction = m_instruction;
instruction.name = m_nameEdit->text().trimmed();
instruction.body = m_bodyEdit->toPlainText().trimmed();
return instruction;
}
} // namespace QodeAssist

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2024-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>
#include "CustomInstructionsManager.hpp"
class QLineEdit;
class QPlainTextEdit;
namespace QodeAssist {
class AddCustomInstructionDialog : public QDialog
{
Q_OBJECT
public:
explicit AddCustomInstructionDialog(QWidget *parent = nullptr);
explicit AddCustomInstructionDialog(const CustomInstruction &instruction, QWidget *parent = nullptr);
~AddCustomInstructionDialog() override = default;
CustomInstruction getInstruction() const;
private:
void setupUi();
QLineEdit *m_nameEdit;
QPlainTextEdit *m_bodyEdit;
CustomInstruction m_instruction;
};
} // namespace QodeAssist

View File

@ -0,0 +1,225 @@
/*
* Copyright (C) 2024-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 "CustomInstructionsManager.hpp"
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <coreplugin/icore.h>
#include <logger/Logger.hpp>
namespace QodeAssist {
CustomInstructionsManager::CustomInstructionsManager(QObject *parent)
: QObject(parent)
{}
CustomInstructionsManager &CustomInstructionsManager::instance()
{
static CustomInstructionsManager instance;
return instance;
}
QString CustomInstructionsManager::getInstructionsDirectory() const
{
QString path = QString("%1/qodeassist/quick_refactor/instructions")
.arg(Core::ICore::userResourcePath().toFSPathString());
return path;
}
bool CustomInstructionsManager::ensureDirectoryExists() const
{
QDir dir(getInstructionsDirectory());
if (!dir.exists()) {
return dir.mkpath(".");
}
return true;
}
bool CustomInstructionsManager::loadInstructions()
{
m_instructions.clear();
if (!ensureDirectoryExists()) {
LOG_MESSAGE("Failed to create instructions directory");
return false;
}
QDir dir(getInstructionsDirectory());
QStringList filters;
filters << "*.json";
QFileInfoList files = dir.entryInfoList(filters, QDir::Files);
for (const QFileInfo &fileInfo : files) {
QFile file(fileInfo.absoluteFilePath());
if (!file.open(QIODevice::ReadOnly)) {
LOG_MESSAGE(QString("Failed to open instruction file: %1").arg(fileInfo.fileName()));
continue;
}
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
if (error.error != QJsonParseError::NoError) {
LOG_MESSAGE(
QString("Failed to parse instruction file %1: %2")
.arg(fileInfo.fileName(), error.errorString()));
continue;
}
QJsonObject obj = doc.object();
CustomInstruction instruction;
instruction.id = obj["id"].toString();
instruction.name = obj["name"].toString();
instruction.body = obj["body"].toString();
if (instruction.id.isEmpty() || instruction.name.isEmpty()) {
LOG_MESSAGE(QString("Invalid instruction in file: %1").arg(fileInfo.fileName()));
continue;
}
m_instructions.append(instruction);
}
LOG_MESSAGE(QString("Loaded %1 custom instructions").arg(m_instructions.size()));
return true;
}
bool CustomInstructionsManager::saveInstruction(const CustomInstruction &instruction)
{
if (!ensureDirectoryExists()) {
LOG_MESSAGE("Failed to create instructions directory");
return false;
}
CustomInstruction newInstruction = instruction;
QString oldFileName;
if (newInstruction.id.isEmpty()) {
newInstruction.id = QUuid::createUuid().toString(QUuid::WithoutBraces);
} else {
// Check if instruction with this ID already exists and get old file name
for (int i = 0; i < m_instructions.size(); ++i) {
if (m_instructions[i].id == newInstruction.id) {
// Build old filename to delete it if name changed
QString oldName = m_instructions[i].name;
oldName.replace(' ', '_');
oldFileName = QString("%1/%2_%3.json")
.arg(getInstructionsDirectory(), oldName, newInstruction.id);
break;
}
}
}
int existingIndex = -1;
for (int i = 0; i < m_instructions.size(); ++i) {
if (m_instructions[i].id == newInstruction.id) {
existingIndex = i;
break;
}
}
QJsonObject obj;
obj["id"] = newInstruction.id;
obj["name"] = newInstruction.name;
obj["body"] = newInstruction.body;
obj["version"] = "0.1";
QJsonDocument doc(obj);
QString sanitizedName = newInstruction.name;
sanitizedName.replace(' ', '_');
QString fileName = QString("%1/%2_%3.json")
.arg(getInstructionsDirectory(), sanitizedName, newInstruction.id);
if (!oldFileName.isEmpty() && oldFileName != fileName) {
QFile::remove(oldFileName);
}
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
LOG_MESSAGE(QString("Failed to save instruction to file: %1").arg(fileName));
return false;
}
if (file.write(doc.toJson(QJsonDocument::Indented)) == -1) {
LOG_MESSAGE(QString("Failed to write instruction data: %1").arg(file.errorString()));
return false;
}
if (existingIndex >= 0) {
m_instructions[existingIndex] = newInstruction;
} else {
m_instructions.append(newInstruction);
}
emit instructionsChanged();
LOG_MESSAGE(QString("Saved custom instruction: %1").arg(newInstruction.name));
return true;
}
bool CustomInstructionsManager::deleteInstruction(const QString &id)
{
int index = -1;
for (int i = 0; i < m_instructions.size(); ++i) {
if (m_instructions[i].id == id) {
index = i;
break;
}
}
if (index < 0) {
LOG_MESSAGE(QString("Instruction not found: %1").arg(id));
return false;
}
QString sanitizedName = m_instructions[index].name;
sanitizedName.replace(' ', '_');
QString fileName = QString("%1/%2_%3.json")
.arg(getInstructionsDirectory(), sanitizedName, id);
QFile file(fileName);
if (!file.remove()) {
LOG_MESSAGE(QString("Failed to delete instruction file: %1").arg(fileName));
return false;
}
m_instructions.removeAt(index);
emit instructionsChanged();
LOG_MESSAGE(QString("Deleted custom instruction with id: %1").arg(id));
return true;
}
CustomInstruction CustomInstructionsManager::getInstructionById(const QString &id) const
{
for (const CustomInstruction &instruction : m_instructions) {
if (instruction.id == id) {
return instruction;
}
}
return CustomInstruction();
}
} // namespace QodeAssist

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2024-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 <QObject>
#include <QString>
#include <QVector>
namespace QodeAssist {
struct CustomInstruction
{
QString id;
QString name;
QString body;
};
class CustomInstructionsManager : public QObject
{
Q_OBJECT
public:
static CustomInstructionsManager &instance();
bool loadInstructions();
bool saveInstruction(const CustomInstruction &instruction);
bool deleteInstruction(const QString &id);
QVector<CustomInstruction> instructions() const { return m_instructions; }
CustomInstruction getInstructionById(const QString &id) const;
signals:
void instructionsChanged();
private:
explicit CustomInstructionsManager(QObject *parent = nullptr);
~CustomInstructionsManager() override = default;
QString getInstructionsDirectory() const;
bool ensureDirectoryExists() const;
QVector<CustomInstruction> m_instructions;
};
} // namespace QodeAssist

View File

@ -18,19 +18,31 @@
*/
#include "QuickRefactorDialog.hpp"
#include "AddCustomInstructionDialog.hpp"
#include "CustomInstructionsManager.hpp"
#include "QodeAssisttr.h"
#include <QApplication>
#include <QComboBox>
#include <QCompleter>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QDir>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QScreen>
#include <QStringListModel>
#include <QTimer>
#include <QToolButton>
#include <QUrl>
#include <QVBoxLayout>
#include <coreplugin/icore.h>
#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
@ -63,17 +75,79 @@ void QuickRefactorDialog::setupUi()
actionsLayout->addStretch();
mainLayout->addLayout(actionsLayout);
m_instructionsLabel = new QLabel(Tr::tr("Enter refactoring instructions:"), this);
QHBoxLayout *instructionsLayout = new QHBoxLayout();
instructionsLayout->setSpacing(4);
QLabel *instructionsLabel = new QLabel(Tr::tr("Custom Instructions:"), this);
instructionsLayout->addWidget(instructionsLabel);
m_commandsComboBox = new QComboBox(this);
m_commandsComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_commandsComboBox->setEditable(true);
m_commandsComboBox->setInsertPolicy(QComboBox::NoInsert);
m_commandsComboBox->lineEdit()->setPlaceholderText("Search or select instruction...");
QCompleter *completer = new QCompleter(this);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setFilterMode(Qt::MatchContains);
m_commandsComboBox->setCompleter(completer);
instructionsLayout->addWidget(m_commandsComboBox);
m_addCommandButton = new QToolButton(this);
m_addCommandButton->setText("+");
m_addCommandButton->setToolTip(Tr::tr("Add Custom Instruction"));
instructionsLayout->addWidget(m_addCommandButton);
m_editCommandButton = new QToolButton(this);
m_editCommandButton->setText("");
m_editCommandButton->setToolTip(Tr::tr("Edit Custom Instruction"));
instructionsLayout->addWidget(m_editCommandButton);
m_deleteCommandButton = new QToolButton(this);
m_deleteCommandButton->setText("");
m_deleteCommandButton->setToolTip(Tr::tr("Delete Custom Instruction"));
instructionsLayout->addWidget(m_deleteCommandButton);
m_openFolderButton = new QToolButton(this);
m_openFolderButton->setText("📁");
m_openFolderButton->setToolTip(Tr::tr("Open Instructions Folder"));
instructionsLayout->addWidget(m_openFolderButton);
mainLayout->addLayout(instructionsLayout);
m_instructionsLabel = new QLabel(Tr::tr("Additional instructions (optional):"), this);
mainLayout->addWidget(m_instructionsLabel);
m_textEdit = new QPlainTextEdit(this);
m_textEdit->setMinimumHeight(100);
m_textEdit->setPlaceholderText(Tr::tr("Type your refactoring instructions here..."));
m_textEdit->setPlaceholderText(Tr::tr("Add extra details or modifications to the selected instruction..."));
connect(m_textEdit, &QPlainTextEdit::textChanged, this, &QuickRefactorDialog::updateDialogSize);
connect(
m_commandsComboBox,
QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
&QuickRefactorDialog::onCommandSelected);
connect(m_addCommandButton, &QToolButton::clicked, this, &QuickRefactorDialog::onAddCustomCommand);
connect(
m_editCommandButton, &QToolButton::clicked, this, &QuickRefactorDialog::onEditCustomCommand);
connect(
m_deleteCommandButton,
&QToolButton::clicked,
this,
&QuickRefactorDialog::onDeleteCustomCommand);
connect(
m_openFolderButton,
&QToolButton::clicked,
this,
&QuickRefactorDialog::onOpenInstructionsFolder);
mainLayout->addWidget(m_textEdit);
loadCustomCommands();
QDialogButtonBox *buttonBox
= new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
@ -113,7 +187,22 @@ void QuickRefactorDialog::createActionButtons()
QString QuickRefactorDialog::instructions() const
{
return m_textEdit->toPlainText();
QString result;
CustomInstruction instruction = findCurrentInstruction();
if (!instruction.id.isEmpty()) {
result = instruction.body;
}
QString additionalText = m_textEdit->toPlainText().trimmed();
if (!additionalText.isEmpty()) {
if (!result.isEmpty()) {
result += "\n\n";
}
result += additionalText;
}
return result;
}
void QuickRefactorDialog::setInstructions(const QString &instructions)
@ -145,6 +234,8 @@ bool QuickRefactorDialog::eventFilter(QObject *watched, QEvent *event)
void QuickRefactorDialog::useLastInstructions()
{
if (!m_lastInstructions.isEmpty()) {
m_commandsComboBox->setCurrentIndex(0);
m_commandsComboBox->clearEditText(); // Clear search text
m_textEdit->setPlainText(m_lastInstructions);
m_selectedAction = Action::RepeatLast;
}
@ -153,6 +244,8 @@ void QuickRefactorDialog::useLastInstructions()
void QuickRefactorDialog::useImproveCodeTemplate()
{
m_commandsComboBox->setCurrentIndex(0);
m_commandsComboBox->clearEditText(); // Clear search text
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."));
@ -162,6 +255,8 @@ void QuickRefactorDialog::useImproveCodeTemplate()
void QuickRefactorDialog::useAlternativeSolutionTemplate()
{
m_commandsComboBox->setCurrentIndex(0);
m_commandsComboBox->clearEditText(); // Clear search text
m_textEdit->setPlainText(
Tr::tr("Suggest an alternative implementation approach for the selected code. "
"Provide a different solution that might be cleaner, more efficient, "
@ -214,4 +309,155 @@ void QuickRefactorDialog::updateDialogSize()
resize(newWidth, newHeight);
}
void QuickRefactorDialog::loadCustomCommands()
{
m_commandsComboBox->clear();
m_commandsComboBox->addItem("", QString()); // Empty item for no selection
auto &manager = CustomInstructionsManager::instance();
const QVector<CustomInstruction> &instructions = manager.instructions();
QStringList instructionNames;
for (const CustomInstruction &instruction : instructions) {
m_commandsComboBox->addItem(instruction.name, instruction.id);
instructionNames.append(instruction.name);
}
if (m_commandsComboBox->completer()) {
QStringListModel *model = new QStringListModel(instructionNames, this);
m_commandsComboBox->completer()->setModel(model);
}
bool hasInstructions = !instructions.isEmpty();
m_editCommandButton->setEnabled(hasInstructions);
m_deleteCommandButton->setEnabled(hasInstructions);
}
CustomInstruction QuickRefactorDialog::findCurrentInstruction() const
{
QString currentText = m_commandsComboBox->currentText().trimmed();
if (currentText.isEmpty()) {
return CustomInstruction();
}
auto &manager = CustomInstructionsManager::instance();
const QVector<CustomInstruction> &instructions = manager.instructions();
for (const CustomInstruction &instruction : instructions) {
if (instruction.name == currentText) {
return instruction;
}
}
int currentIndex = m_commandsComboBox->currentIndex();
if (currentIndex > 0) {
QString instructionId = m_commandsComboBox->itemData(currentIndex).toString();
if (!instructionId.isEmpty()) {
return manager.getInstructionById(instructionId);
}
}
return CustomInstruction();
}
void QuickRefactorDialog::onCommandSelected(int index)
{
Q_UNUSED(index);
}
void QuickRefactorDialog::onAddCustomCommand()
{
AddCustomInstructionDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
CustomInstruction instruction = dialog.getInstruction();
auto &manager = CustomInstructionsManager::instance();
if (manager.saveInstruction(instruction)) {
loadCustomCommands();
m_commandsComboBox->setCurrentText(instruction.name);
m_textEdit->clear();
} else {
QMessageBox::warning(
this,
Tr::tr("Error"),
Tr::tr("Failed to save custom instruction. Check logs for details."));
}
}
}
void QuickRefactorDialog::onEditCustomCommand()
{
CustomInstruction instruction = findCurrentInstruction();
if (instruction.id.isEmpty()) {
QMessageBox::information(
this, Tr::tr("No Instruction Selected"), Tr::tr("Please select an instruction to edit."));
return;
}
AddCustomInstructionDialog dialog(instruction, this);
if (dialog.exec() == QDialog::Accepted) {
CustomInstruction updatedInstruction = dialog.getInstruction();
auto &manager = CustomInstructionsManager::instance();
if (manager.saveInstruction(updatedInstruction)) {
loadCustomCommands();
m_commandsComboBox->setCurrentText(updatedInstruction.name);
m_textEdit->clear();
} else {
QMessageBox::warning(
this,
Tr::tr("Error"),
Tr::tr("Failed to update custom instruction. Check logs for details."));
}
}
}
void QuickRefactorDialog::onDeleteCustomCommand()
{
CustomInstruction instruction = findCurrentInstruction();
if (instruction.id.isEmpty()) {
QMessageBox::information(
this, Tr::tr("No Instruction Selected"), Tr::tr("Please select an instruction to delete."));
return;
}
QMessageBox::StandardButton reply = QMessageBox::question(
this,
Tr::tr("Confirm Deletion"),
Tr::tr("Are you sure you want to delete the instruction '%1'?").arg(instruction.name),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
auto &manager = CustomInstructionsManager::instance();
if (manager.deleteInstruction(instruction.id)) {
loadCustomCommands();
m_commandsComboBox->setCurrentIndex(0);
m_commandsComboBox->clearEditText();
} else {
QMessageBox::warning(
this,
Tr::tr("Error"),
Tr::tr("Failed to delete custom instruction. Check logs for details."));
}
}
}
void QuickRefactorDialog::onOpenInstructionsFolder()
{
QString path = QString("%1/qodeassist/quick_refactor/instructions")
.arg(Core::ICore::userResourcePath().toFSPathString());
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
QUrl url = QUrl::fromLocalFile(dir.absolutePath());
QDesktopServices::openUrl(url);
}
} // namespace QodeAssist

View File

@ -21,10 +21,12 @@
#include <QDialog>
#include <QString>
#include "CustomInstructionsManager.hpp"
class QPlainTextEdit;
class QToolButton;
class QLabel;
class QComboBox;
namespace QodeAssist {
@ -51,15 +53,27 @@ private slots:
void useImproveCodeTemplate();
void useAlternativeSolutionTemplate();
void updateDialogSize();
void onCommandSelected(int index);
void onAddCustomCommand();
void onEditCustomCommand();
void onDeleteCustomCommand();
void onOpenInstructionsFolder();
void loadCustomCommands();
private:
void setupUi();
void createActionButtons();
CustomInstruction findCurrentInstruction() const;
QPlainTextEdit *m_textEdit;
QToolButton *m_repeatButton;
QToolButton *m_improveButton;
QToolButton *m_alternativeButton;
QToolButton *m_addCommandButton;
QToolButton *m_editCommandButton;
QToolButton *m_deleteCommandButton;
QToolButton *m_openFolderButton;
QComboBox *m_commandsComboBox;
QLabel *m_instructionsLabel;
Action m_selectedAction = Action::Custom;