mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2025-05-25 18:00:48 -04:00
* feat: Add quick refactor command via context menu * feat: Add settings for Quick Refactor
294 lines
10 KiB
C++
294 lines
10 KiB
C++
/*
|
|
* 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 "QuickRefactorHandler.hpp"
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QUuid>
|
|
|
|
#include <context/DocumentContextReader.hpp>
|
|
#include <context/DocumentReaderQtCreator.hpp>
|
|
#include <context/Utils.hpp>
|
|
#include <llmcore/PromptTemplateManager.hpp>
|
|
#include <llmcore/ProvidersManager.hpp>
|
|
#include <logger/Logger.hpp>
|
|
#include <settings/ChatAssistantSettings.hpp>
|
|
#include <settings/GeneralSettings.hpp>
|
|
|
|
namespace QodeAssist {
|
|
|
|
QuickRefactorHandler::QuickRefactorHandler(QObject *parent)
|
|
: QObject(parent)
|
|
, m_requestHandler(new LLMCore::RequestHandler(this))
|
|
, m_currentEditor(nullptr)
|
|
, m_isRefactoringInProgress(false)
|
|
, m_contextManager(this)
|
|
{
|
|
connect(
|
|
m_requestHandler,
|
|
&LLMCore::RequestHandler::completionReceived,
|
|
this,
|
|
&QuickRefactorHandler::handleLLMResponse);
|
|
|
|
connect(
|
|
m_requestHandler,
|
|
&LLMCore::RequestHandler::requestFinished,
|
|
this,
|
|
[this](const QString &requestId, bool success, const QString &errorString) {
|
|
if (!success && requestId == m_lastRequestId) {
|
|
m_isRefactoringInProgress = false;
|
|
RefactorResult result;
|
|
result.success = false;
|
|
result.errorMessage = errorString;
|
|
emit refactoringCompleted(result);
|
|
}
|
|
});
|
|
}
|
|
|
|
QuickRefactorHandler::~QuickRefactorHandler() {}
|
|
|
|
void QuickRefactorHandler::sendRefactorRequest(
|
|
TextEditor::TextEditorWidget *editor, const QString &instructions)
|
|
{
|
|
if (m_isRefactoringInProgress) {
|
|
cancelRequest();
|
|
}
|
|
|
|
m_currentEditor = editor;
|
|
|
|
Utils::Text::Range range;
|
|
if (editor->textCursor().hasSelection()) {
|
|
QTextCursor cursor = editor->textCursor();
|
|
int startPos = cursor.selectionStart();
|
|
int endPos = cursor.selectionEnd();
|
|
|
|
QTextBlock startBlock = editor->document()->findBlock(startPos);
|
|
int startLine = startBlock.blockNumber() + 1;
|
|
int startColumn = startPos - startBlock.position();
|
|
|
|
QTextBlock endBlock = editor->document()->findBlock(endPos);
|
|
int endLine = endBlock.blockNumber() + 1;
|
|
int endColumn = endPos - endBlock.position();
|
|
|
|
Utils::Text::Position startPosition;
|
|
startPosition.line = startLine;
|
|
startPosition.column = startColumn;
|
|
|
|
Utils::Text::Position endPosition;
|
|
endPosition.line = endLine;
|
|
endPosition.column = endColumn;
|
|
|
|
range = Utils::Text::Range();
|
|
range.begin = startPosition;
|
|
range.end = endPosition;
|
|
} else {
|
|
QTextCursor cursor = editor->textCursor();
|
|
int cursorPos = cursor.position();
|
|
|
|
QTextBlock block = editor->document()->findBlock(cursorPos);
|
|
int line = block.blockNumber() + 1;
|
|
int column = cursorPos - block.position();
|
|
|
|
Utils::Text::Position cursorPosition;
|
|
cursorPosition.line = line;
|
|
cursorPosition.column = column;
|
|
range = Utils::Text::Range();
|
|
range.begin = cursorPosition;
|
|
range.end = cursorPosition;
|
|
}
|
|
|
|
m_currentRange = range;
|
|
prepareAndSendRequest(editor, instructions, range);
|
|
}
|
|
|
|
void QuickRefactorHandler::prepareAndSendRequest(
|
|
TextEditor::TextEditorWidget *editor,
|
|
const QString &instructions,
|
|
const Utils::Text::Range &range)
|
|
{
|
|
auto &settings = Settings::generalSettings();
|
|
|
|
auto &providerRegistry = LLMCore::ProvidersManager::instance();
|
|
auto &promptManager = LLMCore::PromptTemplateManager::instance();
|
|
|
|
const auto providerName = settings.caProvider();
|
|
auto provider = providerRegistry.getProviderByName(providerName);
|
|
|
|
if (!provider) {
|
|
LOG_MESSAGE(QString("No provider found with name: %1").arg(providerName));
|
|
RefactorResult result;
|
|
result.success = false;
|
|
result.errorMessage = QString("No provider found with name: %1").arg(providerName);
|
|
emit refactoringCompleted(result);
|
|
return;
|
|
}
|
|
|
|
const auto templateName = settings.caTemplate();
|
|
auto promptTemplate = promptManager.getChatTemplateByName(templateName);
|
|
|
|
if (!promptTemplate) {
|
|
LOG_MESSAGE(QString("No template found with name: %1").arg(templateName));
|
|
RefactorResult result;
|
|
result.success = false;
|
|
result.errorMessage = QString("No template found with name: %1").arg(templateName);
|
|
emit refactoringCompleted(result);
|
|
return;
|
|
}
|
|
|
|
LLMCore::LLMConfig config;
|
|
config.requestType = LLMCore::RequestType::Chat;
|
|
config.provider = provider;
|
|
config.promptTemplate = promptTemplate;
|
|
config.url = QString("%1%2").arg(settings.caUrl(), provider->chatEndpoint());
|
|
config.providerRequest
|
|
= {{"model", settings.caModel()}, {"stream", Settings::chatAssistantSettings().stream()}};
|
|
config.apiKey = provider->apiKey();
|
|
|
|
LLMCore::ContextData context = prepareContext(editor, range, instructions);
|
|
|
|
provider
|
|
->prepareRequest(config.providerRequest, promptTemplate, context, LLMCore::RequestType::Chat);
|
|
|
|
QString requestId = QUuid::createUuid().toString();
|
|
m_lastRequestId = requestId;
|
|
QJsonObject request{{"id", requestId}};
|
|
|
|
m_isRefactoringInProgress = true;
|
|
|
|
m_requestHandler->sendLLMRequest(config, request);
|
|
}
|
|
|
|
LLMCore::ContextData QuickRefactorHandler::prepareContext(
|
|
TextEditor::TextEditorWidget *editor,
|
|
const Utils::Text::Range &range,
|
|
const QString &instructions)
|
|
{
|
|
LLMCore::ContextData context;
|
|
|
|
auto textDocument = editor->textDocument();
|
|
Context::DocumentReaderQtCreator documentReader;
|
|
auto documentInfo = documentReader.readDocument(textDocument->filePath().toUrlishString());
|
|
|
|
if (!documentInfo.document) {
|
|
LOG_MESSAGE("Error: Document is not available");
|
|
return context;
|
|
}
|
|
|
|
QTextCursor cursor = editor->textCursor();
|
|
int cursorPos = cursor.position();
|
|
|
|
// TODO add selecting content before and after cursor/selection
|
|
QString fullContent = documentInfo.document->toPlainText();
|
|
QString taggedContent = fullContent;
|
|
|
|
if (cursor.hasSelection()) {
|
|
int selEnd = cursor.selectionEnd();
|
|
int selStart = cursor.selectionStart();
|
|
taggedContent
|
|
.insert(selEnd, selEnd == cursorPos ? "<selection_end><cursor>" : "<selection_end>");
|
|
taggedContent.insert(
|
|
selStart, selStart == cursorPos ? "<cursor><selection_start>" : "<selection_start>");
|
|
} else {
|
|
taggedContent.insert(cursorPos, "<cursor>");
|
|
}
|
|
|
|
QString systemPrompt = Settings::codeCompletionSettings().quickRefactorSystemPrompt();
|
|
systemPrompt += "\n\nFile information:";
|
|
systemPrompt += "\nLanguage: " + documentInfo.mimeType;
|
|
systemPrompt += "\nFile path: " + documentInfo.filePath;
|
|
|
|
systemPrompt += "\n\nCode context with position markers:";
|
|
systemPrompt += taggedContent;
|
|
|
|
systemPrompt += "\n\nOutput format:";
|
|
systemPrompt += "\n- Generate ONLY the code that should replace the current selection "
|
|
"between<selection_start><selection_end> or be "
|
|
"inserted at cursor position<cursor>";
|
|
systemPrompt += "\n- Do not include any explanations, comments about the code, or markdown "
|
|
"code block markers";
|
|
systemPrompt += "\n- The output should be ready to insert directly into the editor";
|
|
systemPrompt += "\n- Follow the existing code style and indentation patterns";
|
|
|
|
if (Settings::codeCompletionSettings().useOpenFilesInQuickRefactor()) {
|
|
systemPrompt += "\n\n" + m_contextManager.openedFilesContext({documentInfo.filePath});
|
|
}
|
|
|
|
context.systemPrompt = systemPrompt;
|
|
|
|
QVector<LLMCore::Message> messages;
|
|
messages.append(
|
|
{"user",
|
|
instructions.isEmpty() ? "Refactor the code to improve its quality and maintainability."
|
|
: instructions});
|
|
context.history = messages;
|
|
|
|
return context;
|
|
}
|
|
|
|
void QuickRefactorHandler::handleLLMResponse(
|
|
const QString &response, const QJsonObject &request, bool isComplete)
|
|
{
|
|
if (request["id"].toString() != m_lastRequestId) {
|
|
return;
|
|
}
|
|
|
|
if (isComplete) {
|
|
QString cleanedResponse = response.trimmed();
|
|
if (cleanedResponse.startsWith("```")) {
|
|
int firstNewLine = cleanedResponse.indexOf('\n');
|
|
int lastFence = cleanedResponse.lastIndexOf("```");
|
|
|
|
if (firstNewLine != -1 && lastFence > firstNewLine) {
|
|
cleanedResponse
|
|
= cleanedResponse.mid(firstNewLine + 1, lastFence - firstNewLine - 1).trimmed();
|
|
} else if (lastFence != -1) {
|
|
cleanedResponse = cleanedResponse.mid(3, lastFence - 3).trimmed();
|
|
}
|
|
}
|
|
|
|
RefactorResult result;
|
|
result.newText = cleanedResponse;
|
|
result.insertRange = m_currentRange;
|
|
result.success = true;
|
|
|
|
LOG_MESSAGE("Refactoring completed successfully. New code to insert: ");
|
|
LOG_MESSAGE("---------- BEGIN REFACTORED CODE ----------");
|
|
LOG_MESSAGE(cleanedResponse);
|
|
LOG_MESSAGE("----------- END REFACTORED CODE -----------");
|
|
|
|
emit refactoringCompleted(result);
|
|
}
|
|
}
|
|
|
|
void QuickRefactorHandler::cancelRequest()
|
|
{
|
|
if (m_isRefactoringInProgress) {
|
|
m_requestHandler->cancelRequest(m_lastRequestId);
|
|
m_isRefactoringInProgress = false;
|
|
|
|
RefactorResult result;
|
|
result.success = false;
|
|
result.errorMessage = "Refactoring request was cancelled";
|
|
emit refactoringCompleted(result);
|
|
}
|
|
}
|
|
|
|
} // namespace QodeAssist
|