refactor: Restructure project into sources/tests layout and dissolve PluginLLMCore

This commit is contained in:
Petr Mironychev
2026-07-14 02:50:43 +02:00
parent adef7972ed
commit 34d4f2c517
348 changed files with 809 additions and 852 deletions

View File

@@ -0,0 +1,236 @@
// Copyright (C) 2025-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include "ConfigurationManager.hpp"
#include <settings/ButtonAspect.hpp>
#include <QTimer>
#include "plugin/QodeAssisttr.h"
namespace QodeAssist {
ConfigurationManager &ConfigurationManager::instance()
{
static ConfigurationManager instance;
return instance;
}
void ConfigurationManager::init()
{
setupConnections();
updateAllTemplateDescriptions();
checkAllTemplate();
}
void ConfigurationManager::updateTemplateDescription(const Utils::StringAspect &templateAspect)
{
Templates::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
if (!templ) {
return;
}
if (&templateAspect == &m_generalSettings.ccTemplate) {
m_generalSettings.ccTemplateDescription.setValue(templ->description());
} else if (&templateAspect == &m_generalSettings.caTemplate) {
m_generalSettings.caTemplateDescription.setValue(templ->description());
} else if (&templateAspect == &m_generalSettings.qrTemplate) {
m_generalSettings.qrTemplateDescription.setValue(templ->description());
}
}
void ConfigurationManager::updateAllTemplateDescriptions()
{
updateTemplateDescription(m_generalSettings.ccTemplate);
updateTemplateDescription(m_generalSettings.caTemplate);
updateTemplateDescription(m_generalSettings.qrTemplate);
}
void ConfigurationManager::checkTemplate(const Utils::StringAspect &templateAspect)
{
Templates::PromptTemplate *templ = m_templateManger.getFimTemplateByName(templateAspect.value());
if (templ->name() == templateAspect.value())
return;
if (&templateAspect == &m_generalSettings.ccTemplate) {
m_generalSettings.ccTemplate.setValue(templ->name());
} else if (&templateAspect == &m_generalSettings.caTemplate) {
m_generalSettings.caTemplate.setValue(templ->name());
}
}
void ConfigurationManager::checkAllTemplate()
{
checkTemplate(m_generalSettings.ccTemplate);
checkTemplate(m_generalSettings.caTemplate);
}
ConfigurationManager::ConfigurationManager(QObject *parent)
: QObject(parent)
, m_generalSettings(Settings::generalSettings())
, m_providersManager(Providers::ProvidersManager::instance())
, m_templateManger(Templates::PromptTemplateManager::instance())
{}
void ConfigurationManager::setupConnections()
{
using Config = ConfigurationManager;
using Button = ButtonAspect;
connect(&m_generalSettings.ccSelectProvider, &Button::clicked, this, &Config::selectProvider);
connect(&m_generalSettings.caSelectProvider, &Button::clicked, this, &Config::selectProvider);
connect(&m_generalSettings.qrSelectProvider, &Button::clicked, this, &Config::selectProvider);
connect(&m_generalSettings.ccSelectModel, &Button::clicked, this, &Config::selectModel);
connect(&m_generalSettings.caSelectModel, &Button::clicked, this, &Config::selectModel);
connect(&m_generalSettings.qrSelectModel, &Button::clicked, this, &Config::selectModel);
connect(&m_generalSettings.ccSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
connect(&m_generalSettings.caSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
connect(&m_generalSettings.qrSelectTemplate, &Button::clicked, this, &Config::selectTemplate);
connect(&m_generalSettings.ccSetUrl, &Button::clicked, this, &Config::selectUrl);
connect(&m_generalSettings.caSetUrl, &Button::clicked, this, &Config::selectUrl);
connect(&m_generalSettings.qrSetUrl, &Button::clicked, this, &Config::selectUrl);
connect(
&m_generalSettings.ccPreset1SelectProvider, &Button::clicked, this, &Config::selectProvider);
connect(&m_generalSettings.ccPreset1SetUrl, &Button::clicked, this, &Config::selectUrl);
connect(&m_generalSettings.ccPreset1SelectModel, &Button::clicked, this, &Config::selectModel);
connect(
&m_generalSettings.ccPreset1SelectTemplate, &Button::clicked, this, &Config::selectTemplate);
connect(&m_generalSettings.ccTemplate, &Utils::StringAspect::changed, this, [this]() {
updateTemplateDescription(m_generalSettings.ccTemplate);
});
connect(&m_generalSettings.caTemplate, &Utils::StringAspect::changed, this, [this]() {
updateTemplateDescription(m_generalSettings.caTemplate);
});
connect(&m_generalSettings.qrTemplate, &Utils::StringAspect::changed, this, [this]() {
updateTemplateDescription(m_generalSettings.qrTemplate);
});
}
void ConfigurationManager::selectProvider()
{
const auto providersList = m_providersManager.providersNames();
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
if (!settingsButton)
return;
auto &targetSettings = (settingsButton == &m_generalSettings.ccSelectProvider)
? m_generalSettings.ccProvider
: settingsButton == &m_generalSettings.ccPreset1SelectProvider
? m_generalSettings.ccPreset1Provider
: settingsButton == &m_generalSettings.qrSelectProvider
? m_generalSettings.qrProvider
: m_generalSettings.caProvider;
QTimer::singleShot(0, this, [this, providersList, &targetSettings] {
m_generalSettings.showSelectionDialog(
providersList, targetSettings, Tr::tr("Select LLM Provider"), Tr::tr("Providers:"));
});
}
void ConfigurationManager::selectModel()
{
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
if (!settingsButton)
return;
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectModel);
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectModel);
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectModel);
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
: m_generalSettings.caProvider.volatileValue();
const auto providerUrl = isCodeCompletion ? m_generalSettings.ccUrl.volatileValue()
: isPreset1 ? m_generalSettings.ccPreset1Url.volatileValue()
: isQuickRefactor ? m_generalSettings.qrUrl.volatileValue()
: m_generalSettings.caUrl.volatileValue();
auto *targetSettings = &(isCodeCompletion ? m_generalSettings.ccModel
: isPreset1 ? m_generalSettings.ccPreset1Model
: isQuickRefactor ? m_generalSettings.qrModel
: m_generalSettings.caModel);
if (auto provider = m_providersManager.getProviderByName(providerName)) {
if (!provider->capabilities().testFlag(Providers::ProviderCapability::ModelListing)) {
m_generalSettings.showModelsNotSupportedDialog(*targetSettings);
return;
}
provider->getInstalledModels(providerUrl)
.then(this, [this, targetSettings](const QList<QString> &modelList) {
if (modelList.isEmpty()) {
m_generalSettings.showModelsNotFoundDialog(*targetSettings);
return;
}
m_generalSettings.showSelectionDialog(
modelList, *targetSettings, Tr::tr("Select LLM Model"), Tr::tr("Models:"));
});
}
}
void ConfigurationManager::selectTemplate()
{
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
if (!settingsButton)
return;
const bool isCodeCompletion = (settingsButton == &m_generalSettings.ccSelectTemplate);
const bool isPreset1 = (settingsButton == &m_generalSettings.ccPreset1SelectTemplate);
const bool isQuickRefactor = (settingsButton == &m_generalSettings.qrSelectTemplate);
const QString providerName = isCodeCompletion ? m_generalSettings.ccProvider.volatileValue()
: isPreset1 ? m_generalSettings.ccPreset1Provider.volatileValue()
: isQuickRefactor ? m_generalSettings.qrProvider.volatileValue()
: m_generalSettings.caProvider.volatileValue();
auto providerID = m_providersManager.getProviderByName(providerName)->providerID();
const auto templateList = isCodeCompletion || isPreset1
? m_templateManger.getFimTemplatesForProvider(providerID)
: m_templateManger.getChatTemplatesForProvider(providerID);
auto &targetSettings = isCodeCompletion ? m_generalSettings.ccTemplate
: isPreset1 ? m_generalSettings.ccPreset1Template
: isQuickRefactor ? m_generalSettings.qrTemplate
: m_generalSettings.caTemplate;
QTimer::singleShot(0, &m_generalSettings, [this, templateList, &targetSettings]() {
m_generalSettings.showSelectionDialog(
templateList, targetSettings, Tr::tr("Select Template"), Tr::tr("Templates:"));
});
}
void ConfigurationManager::selectUrl()
{
auto *settingsButton = qobject_cast<ButtonAspect *>(sender());
if (!settingsButton)
return;
QStringList urls;
for (const auto &name : m_providersManager.providersNames()) {
const auto url = m_providersManager.getProviderByName(name)->url();
if (!urls.contains(url))
urls.append(url);
}
auto &targetSettings = (settingsButton == &m_generalSettings.ccSetUrl) ? m_generalSettings.ccUrl
: settingsButton == &m_generalSettings.ccPreset1SetUrl
? m_generalSettings.ccPreset1Url
: settingsButton == &m_generalSettings.qrSetUrl
? m_generalSettings.qrUrl
: m_generalSettings.caUrl;
QTimer::singleShot(0, &m_generalSettings, [this, urls, &targetSettings]() {
m_generalSettings.showUrlSelectionDialog(targetSettings, urls);
});
}
} // namespace QodeAssist

View File

@@ -0,0 +1,48 @@
// Copyright (C) 2025-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
#include <QObject>
#include "templates/PromptTemplateManager.hpp"
#include "providers/ProvidersManager.hpp"
#include "settings/GeneralSettings.hpp"
namespace QodeAssist {
class ConfigurationManager : public QObject
{
Q_OBJECT
public:
static ConfigurationManager &instance();
void init();
void updateTemplateDescription(const Utils::StringAspect &templateAspect);
void updateAllTemplateDescriptions();
void checkTemplate(const Utils::StringAspect &templateAspect);
void checkAllTemplate();
public slots:
void selectProvider();
void selectModel();
void selectTemplate();
void selectUrl();
private:
explicit ConfigurationManager(QObject *parent = nullptr);
~ConfigurationManager() = default;
ConfigurationManager(const ConfigurationManager &) = delete;
ConfigurationManager &operator=(const ConfigurationManager &) = delete;
Settings::GeneralSettings &m_generalSettings;
Providers::ProvidersManager &m_providersManager;
Templates::PromptTemplateManager &m_templateManger;
void setupConnections();
};
} // namespace QodeAssist

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
namespace QodeAssist::Constants {
const char ACTION_ID[] = "QodeAssist.Action";
const char MENU_ID[] = "QodeAssist.Menu";
const char QODE_ASSIST_REQUEST_SUGGESTION[] = "QodeAssist.RequestSuggestion";
const char QODE_ASSIST_CHAT_CONTEXT[] = "QodeAssist.ChatContext";
const char QODE_ASSIST_CHAT_NAV_ID[] = "QodeAssistChat";
const char QODE_ASSIST_CHAT_EDITOR_ID[] = "QodeAssist.ChatEditor";
const char QODE_ASSIST_SHOW_CHAT_ACTION[] = "QodeAssist.ShowChatView";
const char QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION[] = "QodeAssist.OpenChatWindow";
const char QODE_ASSIST_NEW_CHAT_ACTION[] = "QodeAssist.NewChat";
const char QODE_ASSIST_CHAT_SEND_MESSAGE[] = "QodeAssist.Chat.SendMessage";
const char QODE_ASSIST_CHAT_CLEAR_SESSION[] = "QodeAssist.Chat.ClearSession";
const char QODE_ASSIST_CHAT_SHOW_IN_RIGHT[] = "QodeAssist.Chat.ShowInRightSidebar";
} // namespace QodeAssist::Constants

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
#include <QCoreApplication>
namespace QodeAssist {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(QtC::QodeAssist)
};
} // namespace QodeAssist

View File

@@ -0,0 +1,78 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include "UpdateStatusWidget.hpp"
#include <QMenu>
namespace QodeAssist {
UpdateStatusWidget::UpdateStatusWidget(QWidget *parent)
: QFrame(parent)
{
setFrameStyle(QFrame::NoFrame);
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(4, 0, 4, 0);
layout->setSpacing(4);
m_actionButton = new QToolButton(this);
m_actionButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_chatButton = new QToolButton(this);
m_chatButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_versionLabel = new QLabel(this);
m_versionLabel->setVisible(false);
m_updateButton = new QPushButton(tr("Update"), this);
m_updateButton->setVisible(false);
m_updateButton->setStyleSheet("QPushButton { padding: 2px 8px; }");
layout->addWidget(m_actionButton);
layout->addWidget(m_chatButton);
layout->addWidget(m_versionLabel);
layout->addWidget(m_updateButton);
}
void UpdateStatusWidget::setDefaultAction(QAction *action)
{
m_actionButton->setDefaultAction(action);
}
void UpdateStatusWidget::showUpdateAvailable(const QString &version)
{
m_versionLabel->setText(tr("New version: v%1").arg(version));
m_versionLabel->setVisible(true);
m_updateButton->setVisible(true);
m_updateButton->setToolTip(tr("Check update information"));
}
void UpdateStatusWidget::hideUpdateInfo()
{
m_versionLabel->setVisible(false);
m_updateButton->setVisible(false);
}
void UpdateStatusWidget::setChatButtonAction(QAction *action)
{
m_chatButton->setDefaultAction(action);
}
void UpdateStatusWidget::setChatButtonMenu(QMenu *menu)
{
m_chatButton->setMenu(menu);
m_chatButton->setPopupMode(QToolButton::DelayedPopup);
m_chatButton->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_chatButton, &QWidget::customContextMenuRequested, m_chatButton, [this, menu](const QPoint &pos) {
menu->exec(m_chatButton->mapToGlobal(pos));
});
}
QPushButton *UpdateStatusWidget::updateButton() const
{
return m_updateButton;
}
} // namespace QodeAssist

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
#include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QPushButton>
#include <QToolButton>
QT_BEGIN_NAMESPACE
class QMenu;
QT_END_NAMESPACE
namespace QodeAssist {
class UpdateStatusWidget : public QFrame
{
Q_OBJECT
public:
explicit UpdateStatusWidget(QWidget *parent = nullptr);
void setDefaultAction(QAction *action);
void showUpdateAvailable(const QString &version);
void hideUpdateInfo();
void setChatButtonAction(QAction *action);
void setChatButtonMenu(QMenu *menu);
QPushButton *updateButton() const;
private:
QToolButton *m_actionButton;
QToolButton *m_chatButton;
QLabel *m_versionLabel;
QPushButton *m_updateButton;
};
} // namespace QodeAssist

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2025 Povilas Kanapickas <povilas@radix.lt>
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#pragma once
#include <QtGlobal>
#define QODEASSIST_QT_CREATOR_VERSION \
QT_VERSION_CHECK( \
QODEASSIST_QT_CREATOR_VERSION_MAJOR, \
QODEASSIST_QT_CREATOR_VERSION_MINOR, \
QODEASSIST_QT_CREATOR_VERSION_PATCH)

View File

@@ -0,0 +1,498 @@
// Copyright (C) 2024-2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
#include <memory>
#include "plugin/QodeAssistConstants.hpp"
#include "plugin/QodeAssisttr.h"
#include "settings/PluginUpdater.hpp"
#include "settings/UpdateDialog.hpp"
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/editormanager/documentmodel.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/navigationwidget.h>
#include <coreplugin/statusbarmanager.h>
#include <extensionsystem/iplugin.h>
#include <languageclient/languageclientmanager.h>
#include <texteditor/texteditor.h>
#include <utils/icon.h>
#include <QAction>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
#include <QTranslator>
#include <QInputDialog>
#include "ConfigurationManager.hpp"
#include "completion/QodeAssistClient.hpp"
#include "UpdateStatusWidget.hpp"
#include "plugin/Version.hpp"
#include "chat/ChatEditor.hpp"
#include "chat/ChatEditorFactory.hpp"
#include "chat/ChatOutputPane.h"
#include "chat/NavigationPanel.hpp"
#include "context/DocumentReaderQtCreator.hpp"
#include "templates/PromptProviderFim.hpp"
#include "providers/ProvidersManager.hpp"
#include "logger/RequestPerformanceLogger.hpp"
#include "mcp/McpClientsManager.hpp"
#include "mcp/McpServerManager.hpp"
#include "skills/SkillsManager.hpp"
#include "tools/ToolsRegistration.hpp"
#include "providers/Providers.hpp"
#include "settings/ChatAssistantSettings.hpp"
#include "settings/GeneralSettings.hpp"
#include "settings/ProjectSettingsPanel.hpp"
#include "settings/QuickRefactorSettings.hpp"
#include "settings/SettingsConstants.hpp"
#include "templates/Templates.hpp"
#include "widgets/CustomInstructionsManager.hpp"
#include "widgets/QuickRefactorDialog.hpp"
#include <ChatView/ChatView.hpp>
#include <ChatView/ChatFileManager.hpp>
#include <ChatView/ChatRootView.hpp>
#include <ChatView/ChatWidget.hpp>
#include <ChatView/SessionFileRegistry.hpp>
#include <coreplugin/editormanager/editormanager.h>
#include <QUuid>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
#include <texteditor/texteditorconstants.h>
using namespace Utils;
using namespace Core;
using namespace ProjectExplorer;
namespace QodeAssist::Internal {
class QodeAssistPlugin final : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "QodeAssist.json")
public:
QodeAssistPlugin()
: m_updater(new PluginUpdater(this))
, m_promptProvider(Templates::PromptTemplateManager::instance())
{}
~QodeAssistPlugin() final
{
Chat::ChatFileManager::cleanupGlobalIntermediateStorage();
delete m_qodeAssistClient;
if (m_chatOutputPane) {
delete m_chatOutputPane;
}
if (m_navigationPanel) {
delete m_navigationPanel;
}
delete m_chatEditorFactory;
}
void loadTranslations()
{
const QString langId = Core::ICore::userInterfaceLanguage();
QTranslator *translator = new QTranslator(qApp);
QString resourcePath = QString(":/translations/QodeAssist_%1.qm").arg(langId);
bool success = translator->load(resourcePath);
if (success) {
qApp->installTranslator(translator);
qDebug() << "Loaded translation from resources:" << resourcePath;
} else {
delete translator;
qDebug() << "No translation found for language:" << langId;
}
}
void initialize() final
{
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(15, 0, 83)
Core::IOptionsPage::registerCategory(
Constants::QODE_ASSIST_GENERAL_OPTIONS_CATEGORY,
Constants::QODE_ASSIST_GENERAL_OPTIONS_DISPLAY_CATEGORY,
":/resources/images/qoderassist-icon.png");
#endif
QQuickWindow::setSceneGraphBackend(
Settings::chatAssistantSettings().chatRenderer.stringValue());
loadTranslations();
Providers::registerProviders();
Templates::registerTemplates();
CustomInstructionsManager::instance().loadInstructions();
Utils::Icon QCODEASSIST_ICON(
{{":/resources/images/qoderassist-icon.png", Utils::Theme::IconsBaseColor}});
Utils::Icon QCODEASSIST_CHAT_ICON(
{{":/resources/images/qode-assist-chat-icon.png", Utils::Theme::IconsBaseColor}});
ActionBuilder requestAction(this, Constants::QODE_ASSIST_REQUEST_SUGGESTION);
requestAction.setToolTip(
Tr::tr("Generate QodeAssist suggestion at the current cursor position."));
requestAction.setText(Tr::tr("Request QodeAssist Suggestion"));
requestAction.setIcon(QCODEASSIST_ICON.icon());
const QKeySequence defaultShortcut = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Q);
requestAction.setDefaultKeySequence(defaultShortcut);
requestAction.addOnTriggered(this, [this] {
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
m_qodeAssistClient->requestCompletions(editor);
} else
qWarning() << "The QodeAssist is not ready. Please check your connection and "
"settings.";
}
});
m_statusWidget = new UpdateStatusWidget;
m_statusWidget->setDefaultAction(requestAction.contextAction());
StatusBarManager::addStatusBarWidget(m_statusWidget, StatusBarManager::RightCorner);
connect(m_statusWidget->updateButton(), &QPushButton::clicked, this, [this]() {
UpdateDialog::checkForUpdatesAndShow(Core::ICore::mainWindow());
});
m_engine = new QQmlEngine{this};
m_sessionFileRegistry = new Chat::SessionFileRegistry{this};
m_skillsManager = new Skills::SkillsManager{this};
{
auto &providers = Providers::ProvidersManager::instance();
for (const QString &providerName : providers.providersNames()) {
if (auto *provider = providers.getProviderByName(providerName)) {
if (auto *toolsManager = provider->toolsManager())
Tools::registerSkillTool(toolsManager, m_skillsManager);
}
}
}
if (Settings::chatAssistantSettings().enableChatInBottomToolBar()) {
m_chatOutputPane = new Chat::ChatOutputPane{
m_engine, m_sessionFileRegistry, m_skillsManager};
}
if (Settings::chatAssistantSettings().enableChatInNavigationPanel()) {
m_navigationPanel = new Chat::NavigationPanel{
m_engine, m_sessionFileRegistry, m_skillsManager};
}
m_chatEditorFactory = new Chat::ChatEditorFactory{
m_engine, m_sessionFileRegistry, m_skillsManager};
Settings::setupProjectPanel();
ConfigurationManager::instance().init();
m_mcpServerManager = new Mcp::McpServerManager(this);
m_mcpServerManager->init();
Mcp::McpClientsManager::instance().init();
if (Settings::generalSettings().enableCheckUpdate()) {
QTimer::singleShot(3000, this, &QodeAssistPlugin::checkForUpdates);
}
ActionBuilder quickRefactorAction(this, "QodeAssist.QuickRefactor");
const QKeySequence quickRefactorShortcut = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_R);
quickRefactorAction.setDefaultKeySequence(quickRefactorShortcut);
quickRefactorAction.setToolTip(Tr::tr("Refactor code using QodeAssist"));
quickRefactorAction.setText(Tr::tr("Quick Refactor with QodeAssist"));
quickRefactorAction.setIcon(QCODEASSIST_ICON.icon());
quickRefactorAction.addOnTriggered(this, [this] {
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
if (m_qodeAssistClient && m_qodeAssistClient->reachable()) {
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.";
}
}
});
ActionBuilder showChatViewAction(this, Constants::QODE_ASSIST_SHOW_CHAT_ACTION);
const QKeySequence showChatViewShortcut = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_W);
showChatViewAction.setDefaultKeySequence(showChatViewShortcut);
showChatViewAction.setToolTip(Tr::tr("Open QodeAssist Chat as an editor tab"));
showChatViewAction.setText(Tr::tr("Show QodeAssist Chat"));
showChatViewAction.setIcon(QCODEASSIST_CHAT_ICON.icon());
showChatViewAction.addOnTriggered(this, [this] { openChatInEditor(); });
m_statusWidget->setChatButtonAction(showChatViewAction.contextAction());
m_chatButtonMenu = new QMenu(m_statusWidget);
connect(
m_chatButtonMenu,
&QMenu::aboutToShow,
this,
&QodeAssistPlugin::rebuildChatButtonMenu);
m_statusWidget->setChatButtonMenu(m_chatButtonMenu);
ActionBuilder closeChatViewAction(this, "QodeAssist.CloseChatView");
const QKeySequence closeChatViewShortcut = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_S);
closeChatViewAction.setDefaultKeySequence(closeChatViewShortcut);
closeChatViewAction.setToolTip(Tr::tr("Close QodeAssist Chat"));
closeChatViewAction.setText(Tr::tr("Close QodeAssist Chat"));
closeChatViewAction.setIcon(QCODEASSIST_CHAT_ICON.icon());
closeChatViewAction.addOnTriggered(this, [this] {
if (m_chatView && m_chatView->isActive() && m_chatView->isVisible()) {
m_chatView->close();
}
});
ActionBuilder openChatWindowAction(this, Constants::QODE_ASSIST_OPEN_CHAT_WINDOW_ACTION);
openChatWindowAction.setText(Tr::tr("Open QodeAssist Chat in Separate Window"));
openChatWindowAction.setToolTip(Tr::tr("Open the QodeAssist chat in a separate window"));
openChatWindowAction.setIcon(QCODEASSIST_CHAT_ICON.icon());
openChatWindowAction.addOnTriggered(this, [this] { openChatInWindow(); });
ActionBuilder newChatAction(this, Constants::QODE_ASSIST_NEW_CHAT_ACTION);
newChatAction.setText(Tr::tr("New QodeAssist Chat"));
newChatAction.setToolTip(Tr::tr("Open a fresh chat in a new editor tab"));
newChatAction.setIcon(QCODEASSIST_CHAT_ICON.icon());
newChatAction.addOnTriggered(this, [this] { openNewChatInEditor(); });
ActionBuilder sendMessageAction(this, Constants::QODE_ASSIST_CHAT_SEND_MESSAGE);
sendMessageAction.setContext(Core::Context(Constants::QODE_ASSIST_CHAT_CONTEXT));
sendMessageAction.setText(Tr::tr("Send QodeAssist Chat Message"));
sendMessageAction.setToolTip(Tr::tr("Send the current message to the LLM"));
sendMessageAction.setDefaultKeySequence(QKeySequence(Qt::Key_Return));
sendMessageAction.addOnTriggered(this, [] {
if (auto chatWidget = Chat::ChatWidget::focusedInstance())
chatWidget->sendMessage();
});
ActionBuilder clearSessionAction(this, Constants::QODE_ASSIST_CHAT_CLEAR_SESSION);
clearSessionAction.setContext(Core::Context(Constants::QODE_ASSIST_CHAT_CONTEXT));
clearSessionAction.setText(Tr::tr("Clear QodeAssist Chat Session"));
clearSessionAction.setToolTip(Tr::tr("Clear the current chat session"));
clearSessionAction.setDefaultKeySequence(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_L));
clearSessionAction.addOnTriggered(this, [] {
if (auto chatWidget = Chat::ChatWidget::focusedInstance())
chatWidget->clearSession();
});
Core::ActionContainer *editorContextMenu = Core::ActionManager::actionContainer(
TextEditor::Constants::M_STANDARDCONTEXTMENU);
if (editorContextMenu) {
editorContextMenu->addSeparator(Core::Context(TextEditor::Constants::C_TEXTEDITOR));
editorContextMenu
->addAction(quickRefactorAction.command(), Core::Constants::G_DEFAULT_THREE);
editorContextMenu->addAction(requestAction.command(), Core::Constants::G_DEFAULT_THREE);
editorContextMenu->addAction(showChatViewAction.command(),
Core::Constants::G_DEFAULT_THREE);
}
Chat::ChatFileManager::cleanupGlobalIntermediateStorage();
}
void extensionsInitialized() final {}
void restartClient()
{
LanguageClient::LanguageClientManager::shutdownClient(m_qodeAssistClient);
m_qodeAssistClient = new QodeAssistClient(new LLMClientInterface(
Settings::generalSettings(),
Settings::codeCompletionSettings(),
Providers::ProvidersManager::instance(),
&m_promptProvider,
m_documentReader,
m_performanceLogger));
}
bool delayedInitialize() final
{
restartClient();
return true;
}
ShutdownFlag aboutToShutdown() final
{
if (!m_qodeAssistClient)
return SynchronousShutdown;
connect(m_qodeAssistClient, &QObject::destroyed, this, &IPlugin::asynchronousShutdownFinished);
return AsynchronousShutdown;
}
private:
void openChatInEditor()
{
if (auto existing = findExistingChatEditor()) {
Core::EditorManager::activateEditor(existing);
existing->consumePendingChatFile();
return;
}
QString title = Tr::tr("QodeAssist Chat");
Core::IEditor *editor = Core::EditorManager::openEditorWithContents(
Constants::QODE_ASSIST_CHAT_EDITOR_ID, &title, {}, QUuid::createUuid().toString());
if (auto chatEditor = qobject_cast<Chat::ChatEditor *>(editor))
chatEditor->consumePendingChatFile();
}
void openNewChatInEditor()
{
QString title = Tr::tr("QodeAssist Chat");
Core::IEditor *editor = Core::EditorManager::openEditorWithContents(
Constants::QODE_ASSIST_CHAT_EDITOR_ID, &title, {}, QUuid::createUuid().toString());
// For the "New Chat" button pending is empty (no-op). For relocate-to-editor it
// carries the handed-off chat file and gets loaded into the freshly opened tab.
if (auto chatEditor = qobject_cast<Chat::ChatEditor *>(editor))
chatEditor->consumePendingChatFile();
}
Chat::ChatEditor *findExistingChatEditor() const
{
const auto entries = Core::DocumentModel::entries();
for (auto *entry : entries) {
if (!entry || !entry->document)
continue;
if (entry->document->id() != Constants::QODE_ASSIST_CHAT_EDITOR_ID)
continue;
const auto editors = Core::DocumentModel::editorsForDocument(entry->document);
for (auto *editor : editors) {
if (auto chatEditor = qobject_cast<Chat::ChatEditor *>(editor))
return chatEditor;
}
}
return nullptr;
}
void openChatInWindow()
{
if (!m_chatView)
m_chatView.reset(new Chat::ChatView{m_engine, m_sessionFileRegistry, m_skillsManager});
if (!m_chatView->isVisible())
m_chatView->show();
m_chatView->raise();
m_chatView->requestActivate();
if (auto rootView = qobject_cast<Chat::ChatRootView *>(m_chatView->rootObject()))
rootView->consumePendingChatFile();
}
void setChatInBottomPaneEnabled(bool enabled)
{
if (enabled && !m_chatOutputPane)
m_chatOutputPane = new Chat::ChatOutputPane{
m_engine, m_sessionFileRegistry, m_skillsManager};
else if (!enabled && m_chatOutputPane)
delete m_chatOutputPane;
Settings::chatAssistantSettings().enableChatInBottomToolBar.setValue(enabled);
Settings::chatAssistantSettings().writeSettings();
}
void setChatInSidebarEnabled(bool enabled)
{
if (enabled && !m_navigationPanel)
m_navigationPanel = new Chat::NavigationPanel{
m_engine, m_sessionFileRegistry, m_skillsManager};
else if (!enabled && m_navigationPanel)
delete m_navigationPanel;
Settings::chatAssistantSettings().enableChatInNavigationPanel.setValue(enabled);
Settings::chatAssistantSettings().writeSettings();
}
void rebuildChatButtonMenu()
{
if (!m_chatButtonMenu)
return;
m_chatButtonMenu->clear();
QAction *paneAction = m_chatButtonMenu->addAction(Tr::tr("Chat in Bottom Panel"));
paneAction->setCheckable(true);
paneAction->setChecked(m_chatOutputPane != nullptr);
connect(paneAction, &QAction::toggled, this, [this](bool on) {
setChatInBottomPaneEnabled(on);
});
QAction *sidebarAction = m_chatButtonMenu->addAction(Tr::tr("Chat in Sidebar"));
sidebarAction->setCheckable(true);
sidebarAction->setChecked(m_navigationPanel != nullptr);
connect(sidebarAction, &QAction::toggled, this, [this](bool on) {
setChatInSidebarEnabled(on);
});
m_chatButtonMenu->addSeparator();
if (m_chatView && m_chatView->isVisible()) {
QAction *editorAction = m_chatButtonMenu->addAction(Tr::tr("Open Chat in Editor"));
connect(editorAction, &QAction::triggered, this, [this] {
if (m_chatView)
m_chatView->close();
openChatInEditor();
});
} else {
QAction *windowAction
= m_chatButtonMenu->addAction(Tr::tr("Open Chat in Separate Window"));
connect(windowAction, &QAction::triggered, this, [this] { openChatInWindow(); });
}
}
void checkForUpdates()
{
connect(
m_updater,
&PluginUpdater::updateCheckFinished,
this,
&QodeAssistPlugin::handleUpdateCheckResult,
Qt::UniqueConnection);
m_updater->checkForUpdates();
}
void handleUpdateCheckResult(const PluginUpdater::UpdateInfo &info)
{
if (!info.isUpdateAvailable)
return;
if (m_statusWidget)
m_statusWidget->showUpdateAvailable(info.version);
}
QPointer<QodeAssistClient> m_qodeAssistClient;
Templates::PromptProviderFim m_promptProvider;
Context::DocumentReaderQtCreator m_documentReader;
RequestPerformanceLogger m_performanceLogger;
QPointer<Chat::ChatOutputPane> m_chatOutputPane;
QPointer<Chat::NavigationPanel> m_navigationPanel;
QPointer<Chat::SessionFileRegistry> m_sessionFileRegistry;
Chat::ChatEditorFactory *m_chatEditorFactory{nullptr};
QPointer<QMenu> m_chatButtonMenu;
QPointer<PluginUpdater> m_updater;
UpdateStatusWidget *m_statusWidget{nullptr};
QString m_lastRefactorInstructions;
std::unique_ptr<Chat::ChatView> m_chatView;
QPointer<Mcp::McpServerManager> m_mcpServerManager;
QPointer<QQmlEngine> m_engine;
QPointer<Skills::SkillsManager> m_skillsManager;
};
} // namespace QodeAssist::Internal
#include <qodeassist.moc>