Copilot: Allow user to disable Copilot

Fixes: QTCREATORBUG-29179
Change-Id: I274de1f13f773fb61b376643b61056b6f14dabaf
Reviewed-by: BogDan Vatra <bogdan@kdab.com>
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-05-23 08:58:07 +02:00
parent f385324bc8
commit bdb31d4348
18 changed files with 353 additions and 24 deletions

View File

@@ -4,9 +4,11 @@ add_qtc_plugin(Copilot
authwidget.cpp authwidget.h
copilot.qrc
copilotclient.cpp copilotclient.h
copilotconstants.h
copilothoverhandler.cpp copilothoverhandler.h
copilotoptionspage.cpp copilotoptionspage.h
copilotplugin.cpp copilotplugin.h
copilotprojectpanel.cpp copilotprojectpanel.h
copilotsettings.cpp copilotsettings.h
copilotsuggestion.cpp copilotsuggestion.h
requests/checkstatus.h

View File

@@ -14,12 +14,15 @@ QtcPlugin {
"copilot.qrc",
"copilotclient.cpp",
"copilotclient.h",
"copilotconstants.h",
"copilothoverhandler.cpp",
"copilothoverhandler.h",
"copilotoptionspage.cpp",
"copilotoptionspage.h",
"copilotplugin.cpp",
"copilotplugin.h",
"copilotprojectpanel.cpp",
"copilotprojectpanel.h",
"copilotsettings.cpp",
"copilotsettings.h",
"copilotsuggestion.cpp",

View File

@@ -1,6 +1,8 @@
<RCC>
<qresource prefix="/copilot">
<file>images/settingscategory_copilot.png</file>
<file>images/settingscategory_copilot@2x.png</file>
</qresource>
<qresource prefix="/copilot">
<file>images/settingscategory_copilot.png</file>
<file>images/settingscategory_copilot@2x.png</file>
<file>images/copilot.png</file>
<file>images/copilot@2x.png</file>
</qresource>
</RCC>

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotclient.h"
#include "copilotconstants.h"
#include "copilotsettings.h"
#include "copilotsuggestion.h"
@@ -9,8 +10,11 @@
#include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientsettings.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/projectmanager.h>
#include <utils/filepath.h>
#include <texteditor/textdocumentlayout.h>
@@ -19,10 +23,13 @@
#include <languageserverprotocol/lsptypes.h>
#include <QTimer>
#include <QToolButton>
using namespace LanguageServerProtocol;
using namespace TextEditor;
using namespace Utils;
using namespace ProjectExplorer;
using namespace Core;
namespace Copilot::Internal {
@@ -47,27 +54,27 @@ CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath)
setSupportedLanguage(langFilter);
start();
auto openDoc = [this](Core::IDocument *document) {
auto openDoc = [this](IDocument *document) {
if (auto *textDocument = qobject_cast<TextDocument *>(document))
openDocument(textDocument);
};
connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened, this, openDoc);
connect(Core::EditorManager::instance(),
&Core::EditorManager::documentClosed,
connect(EditorManager::instance(), &EditorManager::documentOpened, this, openDoc);
connect(EditorManager::instance(),
&EditorManager::documentClosed,
this,
[this](Core::IDocument *document) {
[this](IDocument *document) {
if (auto textDocument = qobject_cast<TextDocument *>(document))
closeDocument(textDocument);
});
for (Core::IDocument *doc : Core::DocumentModel::openedDocuments())
for (IDocument *doc : DocumentModel::openedDocuments())
openDoc(doc);
}
CopilotClient::~CopilotClient()
{
for (Core::IEditor *editor : Core::DocumentModel::editorsForOpenedDocuments()) {
for (IEditor *editor : DocumentModel::editorsForOpenedDocuments()) {
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
}
@@ -75,14 +82,23 @@ CopilotClient::~CopilotClient()
void CopilotClient::openDocument(TextDocument *document)
{
auto project = ProjectManager::projectForFile(document->filePath());
if (!isEnabled(project))
return;
Client::openDocument(document);
connect(document,
&TextDocument::contentsChangedWithPosition,
this,
[this, document](int position, int charsRemoved, int charsAdded) {
Q_UNUSED(charsRemoved)
if (!CopilotSettings::instance().autoComplete.value())
if (!CopilotSettings::instance().autoComplete())
return;
auto project = ProjectManager::projectForFile(document->filePath());
if (!isEnabled(project))
return;
auto textEditor = BaseTextEditor::currentTextEditor();
if (!textEditor || textEditor->document() != document)
return;
@@ -123,6 +139,11 @@ void CopilotClient::scheduleRequest(TextEditorWidget *editor)
void CopilotClient::requestCompletions(TextEditorWidget *editor)
{
auto project = ProjectManager::projectForFile(editor->textDocument()->filePath());
if (!isEnabled(project))
return;
Utils::MultiTextCursor cursor = editor->multiTextCursor();
if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
return;
@@ -214,4 +235,18 @@ void CopilotClient::requestSignInConfirm(
sendMessage(request);
}
bool CopilotClient::canOpenProject(Project *project)
{
return isEnabled(project);
}
bool CopilotClient::isEnabled(Project *project)
{
if (!project)
return CopilotSettings::instance().enableCopilot();
CopilotProjectSettings settings(project);
return settings.isEnabled();
}
} // namespace Copilot::Internal

View File

@@ -46,6 +46,10 @@ public:
const QString &userCode,
std::function<void(const SignInConfirmRequest::Response &response)> callback);
bool canOpenProject(ProjectExplorer::Project *project) override;
bool isEnabled(ProjectExplorer::Project *project);
private:
QMap<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
struct ScheduleData

View File

@@ -0,0 +1,20 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace Copilot::Constants {
const char COPILOT_PROJECT_SETTINGS_ID[] = "Copilot.Project.Settings";
const char ENABLE_COPILOT[] = "Copilot.EnableCopilot";
const char COPILOT_USE_GLOBAL_SETTINGS[] = "Copilot.UseGlobalSettings";
const char COPILOT_TOGGLE[] = "Copilot.Toggle";
const char COPILOT_ENABLE[] = "Copilot.Enable";
const char COPILOT_DISABLE[] = "Copilot.Disable";
const char COPILOT_REQUEST_SUGGESTION[] = "Copilot.RequestSuggestion";
const char COPILOT_GENERAL_OPTIONS_ID[] = "Copilot.General";
const char COPILOT_GENERAL_OPTIONS_CATEGORY[] = "ZY.Copilot";
const char COPILOT_GENERAL_OPTIONS_DISPLAY_CATEGORY[] = "Copilot";
} // namespace Copilot::Constants

View File

@@ -0,0 +1,12 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/icon.h>
namespace Copilot {
static const Utils::Icon COPILOT_ICON({{":/copilot/images/copilot.png",
Utils::Theme::IconsBaseColor}});
}

View File

@@ -4,6 +4,7 @@
#include "copilotoptionspage.h"
#include "authwidget.h"
#include "copilotconstants.h"
#include "copilotsettings.h"
#include "copilottr.h"
@@ -47,6 +48,7 @@ file from the Copilot neovim plugin.
Column {
authWidget, br,
CopilotSettings::instance().enableCopilot, br,
CopilotSettings::instance().nodeJsPath, br,
CopilotSettings::instance().distPath, br,
CopilotSettings::instance().autoComplete, br,
@@ -82,10 +84,10 @@ file from the Copilot neovim plugin.
CopilotOptionsPage::CopilotOptionsPage()
{
setId("Copilot.General");
setId(Constants::COPILOT_GENERAL_OPTIONS_ID);
setDisplayName("Copilot");
setCategory("ZY.Copilot");
setDisplayCategory("Copilot");
setCategory(Constants::COPILOT_GENERAL_OPTIONS_CATEGORY);
setDisplayCategory(Constants::COPILOT_GENERAL_OPTIONS_DISPLAY_CATEGORY);
setCategoryIconPath(":/copilot/images/settingscategory_copilot.png");
setWidgetCreator([] { return new CopilotOptionsPageWidget; });
}

View File

@@ -4,22 +4,30 @@
#include "copilotplugin.h"
#include "copilotclient.h"
#include "copilotconstants.h"
#include "copiloticons.h"
#include "copilotoptionspage.h"
#include "copilotprojectpanel.h"
#include "copilotsettings.h"
#include "copilottr.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/statusbarmanager.h>
#include <languageclient/languageclientmanager.h>
#include <projectexplorer/projectpanelfactory.h>
#include <texteditor/texteditor.h>
#include <QAction>
#include <QToolButton>
using namespace Utils;
using namespace Core;
using namespace ProjectExplorer;
namespace Copilot {
namespace Internal {
@@ -35,17 +43,73 @@ void CopilotPlugin::initialize()
this,
&CopilotPlugin::restartClient);
QAction *action = new QAction(this);
action->setText(Tr::tr("Request Copilot Suggestion"));
action->setToolTip(Tr::tr("Request Copilot Suggestion at the current editors cursor position."));
QAction *requestAction = new QAction(this);
requestAction->setText(Tr::tr("Request Copilot Suggestion"));
requestAction->setToolTip(
Tr::tr("Request Copilot suggestion at the current editor's cursor position."));
QObject::connect(action, &QAction::triggered, this, [this] {
connect(requestAction, &QAction::triggered, this, [this] {
if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
if (m_client->reachable())
m_client->requestCompletions(editor);
}
});
ActionManager::registerAction(action, "Copilot.RequestSuggestion");
ActionManager::registerAction(requestAction, Constants::COPILOT_REQUEST_SUGGESTION);
QAction *disableAction = new QAction(this);
disableAction->setText(Tr::tr("Disable Copilot"));
disableAction->setToolTip(Tr::tr("Disable Copilot."));
connect(disableAction, &QAction::triggered, this, [] {
CopilotSettings::instance().enableCopilot.setValue(true);
CopilotSettings::instance().apply();
});
ActionManager::registerAction(disableAction, Constants::COPILOT_DISABLE);
QAction *enableAction = new QAction(this);
enableAction->setText(Tr::tr("Enable Copilot"));
enableAction->setToolTip(Tr::tr("Enable Copilot."));
connect(enableAction, &QAction::triggered, this, [] {
CopilotSettings::instance().enableCopilot.setValue(false);
CopilotSettings::instance().apply();
});
ActionManager::registerAction(enableAction, Constants::COPILOT_ENABLE);
QAction *toggleAction = new QAction(this);
toggleAction->setText(Tr::tr("Toggle Copilot"));
toggleAction->setCheckable(true);
toggleAction->setChecked(CopilotSettings::instance().enableCopilot.value());
toggleAction->setIcon(COPILOT_ICON.icon());
connect(toggleAction, &QAction::toggled, this, [](bool checked) {
CopilotSettings::instance().enableCopilot.setValue(checked);
CopilotSettings::instance().apply();
});
ActionManager::registerAction(toggleAction, Constants::COPILOT_TOGGLE);
auto updateActions = [toggleAction, requestAction] {
const bool enabled = CopilotSettings::instance().enableCopilot.value();
toggleAction->setToolTip(enabled ? Tr::tr("Disable Copilot.") : Tr::tr("Enable Copilot."));
toggleAction->setChecked(enabled);
requestAction->setEnabled(enabled);
};
connect(&CopilotSettings::instance().enableCopilot,
&BoolAspect::valueChanged,
this,
updateActions);
updateActions();
auto toggleButton = new QToolButton;
toggleButton->setDefaultAction(toggleAction);
StatusBarManager::addStatusBarWidget(toggleButton, StatusBarManager::RightCorner);
auto panelFactory = new ProjectPanelFactory;
panelFactory->setPriority(1000);
panelFactory->setDisplayName(Tr::tr("Copilot"));
panelFactory->setCreateWidgetFunction(&Internal::createCopilotProjectPanel);
ProjectPanelFactory::registerFactory(panelFactory);
}
void CopilotPlugin::extensionsInitialized()

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "copilotprojectpanel.h"
#include "copilotconstants.h"
#include "copilotsettings.h"
#include <projectexplorer/project.h>
#include <projectexplorer/projectsettingswidget.h>
#include <utils/layoutbuilder.h>
#include <QWidget>
using namespace ProjectExplorer;
namespace Copilot::Internal {
class CopilotProjectSettingsWidget : public ProjectExplorer::ProjectSettingsWidget
{
public:
CopilotProjectSettingsWidget()
{
setGlobalSettingsId(Constants::COPILOT_GENERAL_OPTIONS_ID);
setUseGlobalSettingsCheckBoxVisible(true);
}
};
ProjectSettingsWidget *createCopilotProjectPanel(Project *project)
{
using namespace Layouting;
using namespace ProjectExplorer;
auto widget = new CopilotProjectSettingsWidget;
auto settings = new CopilotProjectSettings(project, widget);
QObject::connect(widget,
&ProjectSettingsWidget::useGlobalSettingsChanged,
settings,
&CopilotProjectSettings::setUseGlobalSettings);
widget->setUseGlobalSettings(settings->useGlobalSettings());
widget->setEnabled(!settings->useGlobalSettings());
QObject::connect(widget,
&ProjectSettingsWidget::useGlobalSettingsChanged,
widget,
[widget](bool useGlobal) { widget->setEnabled(!useGlobal); });
// clang-format off
Column {
settings->enableCopilot,
}.attachTo(widget);
// clang-format on
return widget;
}
} // namespace Copilot::Internal

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace ProjectExplorer {
class ProjectSettingsWidget;
class Project;
} // namespace ProjectExplorer
namespace Copilot::Internal {
ProjectExplorer::ProjectSettingsWidget *createCopilotProjectPanel(ProjectExplorer::Project *project);
} // namespace Copilot::Internal

View File

@@ -2,8 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotsettings.h"
#include "copilotconstants.h"
#include "copilottr.h"
#include <projectexplorer/project.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
@@ -11,6 +14,15 @@ using namespace Utils;
namespace Copilot {
static void initEnableAspect(BoolAspect &enableCopilot)
{
enableCopilot.setSettingsKey(Constants::ENABLE_COPILOT);
enableCopilot.setDisplayName(Tr::tr("Enable Copilot"));
enableCopilot.setLabelText(Tr::tr("Enable Copilot"));
enableCopilot.setToolTip(Tr::tr("Enables the Copilot integration."));
enableCopilot.setDefaultValue(true);
}
CopilotSettings &CopilotSettings::instance()
{
static CopilotSettings settings;
@@ -32,7 +44,7 @@ CopilotSettings::CopilotSettings()
FilePath::fromUserInput(
"~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js")};
const FilePath distFromVim = Utils::findOrDefault(searchDirs, &FilePath::exists);
const FilePath distFromVim = findOrDefault(searchDirs, &FilePath::exists);
nodeJsPath.setExpectedKind(PathChooser::ExistingCommand);
nodeJsPath.setDefaultFilePath(nodeFromPath);
@@ -55,10 +67,52 @@ CopilotSettings::CopilotSettings()
"https://github.com/github/copilot.vim#getting-started for installation instructions."));
autoComplete.setDisplayName(Tr::tr("Auto Complete"));
autoComplete.setSettingsKey("Copilot.Autocomplete");
autoComplete.setLabelText(Tr::tr("Request completions automatically"));
autoComplete.setDefaultValue(true);
autoComplete.setToolTip(Tr::tr("Automatically request suggestions for the current text cursor "
"position after changes to the document"));
initEnableAspect(enableCopilot);
}
CopilotProjectSettings::CopilotProjectSettings(ProjectExplorer::Project *project, QObject *parent)
: AspectContainer(parent)
{
setAutoApply(true);
useGlobalSettings.setSettingsKey(Constants::COPILOT_USE_GLOBAL_SETTINGS);
useGlobalSettings.setDefaultValue(true);
initEnableAspect(enableCopilot);
QVariantMap map = project->namedSettings(Constants::COPILOT_PROJECT_SETTINGS_ID).toMap();
fromMap(map);
connect(&enableCopilot, &BoolAspect::valueChanged, this, [this, project] { save(project); });
connect(&useGlobalSettings, &BoolAspect::valueChanged, this, [this, project] { save(project); });
}
void CopilotProjectSettings::setUseGlobalSettings(bool useGlobal)
{
useGlobalSettings.setValue(useGlobal);
}
bool CopilotProjectSettings::isEnabled() const
{
if (useGlobalSettings())
return CopilotSettings::instance().enableCopilot();
return enableCopilot();
}
void CopilotProjectSettings::save(ProjectExplorer::Project *project)
{
QVariantMap map;
toMap(map);
project->setNamedSettings(Constants::COPILOT_PROJECT_SETTINGS_ID, map);
// This triggers a restart of the Copilot language server.
CopilotSettings::instance().apply();
}
} // namespace Copilot

View File

@@ -5,6 +5,10 @@
#include <utils/aspects.h>
namespace ProjectExplorer {
class Project;
}
namespace Copilot {
class CopilotSettings : public Utils::AspectContainer
@@ -17,6 +21,21 @@ public:
Utils::FilePathAspect nodeJsPath{this};
Utils::FilePathAspect distPath{this};
Utils::BoolAspect autoComplete{this};
Utils::BoolAspect enableCopilot{this};
};
class CopilotProjectSettings : public Utils::AspectContainer
{
public:
CopilotProjectSettings(ProjectExplorer::Project *project, QObject *parent = nullptr);
void save(ProjectExplorer::Project *project);
void setUseGlobalSettings(bool useGlobalSettings);
bool isEnabled() const;
Utils::BoolAspect enableCopilot{this};
Utils::BoolAspect useGlobalSettings{this};
};
} // namespace Copilot

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 497 B