// Copyright (C) 2024-2026 Petr Mironychev // SPDX-License-Identifier: GPL-3.0-or-later #include "QodeAssistConstants.hpp" #include "QodeAssisttr.h" #include "settings/PluginUpdater.hpp" #include "settings/UpdateDialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ConfigurationManager.hpp" #include "QodeAssistClient.hpp" #include "UpdateStatusWidget.hpp" #include "Version.hpp" #include "chat/ChatEditor.hpp" #include "chat/ChatEditorFactory.hpp" #include "chat/ChatOutputPane.h" #include "chat/NavigationPanel.hpp" #include "context/DocumentReaderQtCreator.hpp" #include "pluginllmcore/PromptProviderFim.hpp" #include "pluginllmcore/ProvidersManager.hpp" #include "logger/RequestPerformanceLogger.hpp" #include "mcp/McpClientsManager.hpp" #include "mcp/McpServerManager.hpp" #include "sources/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 #include #include #include #include #include #include #include #include #include #include #include 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(PluginLLMCore::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 = PluginLLMCore::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 in an editor split")); showChatViewAction.setText(Tr::tr("Show QodeAssist Chat")); showChatViewAction.setIcon(QCODEASSIST_CHAT_ICON.icon()); showChatViewAction.addOnTriggered(this, [this] { openChatInSplit(); }); 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 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::CTRL | 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(), PluginLLMCore::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 openChatInSplit() { if (auto splitCommand = Core::ActionManager::command(Core::Constants::SPLIT_SIDE_BY_SIDE)) { if (auto splitAction = splitCommand->action()) splitAction->trigger(); } 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(editor)) chatEditor->consumePendingChatFile(); } 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(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 *splitAction = m_chatButtonMenu->addAction(Tr::tr("Open Chat in Split")); connect(splitAction, &QAction::triggered, this, [this] { if (m_chatView) m_chatView->close(); openChatInSplit(); }); } 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 m_qodeAssistClient; PluginLLMCore::PromptProviderFim m_promptProvider; Context::DocumentReaderQtCreator m_documentReader; RequestPerformanceLogger m_performanceLogger; QPointer m_chatOutputPane; QPointer m_navigationPanel; QPointer m_sessionFileRegistry; Chat::ChatEditorFactory *m_chatEditorFactory{nullptr}; QPointer m_chatButtonMenu; QPointer m_updater; UpdateStatusWidget *m_statusWidget{nullptr}; QString m_lastRefactorInstructions; QScopedPointer m_chatView; QPointer m_mcpServerManager; QPointer m_engine; QPointer m_skillsManager; }; } // namespace QodeAssist::Internal #include