Copilot: Add LSP plugin for Copilot

Fixes: QTCREATORBUG-27771
Change-Id: I1249b9a4492427208a70b3e21bf20ac668fc3c50
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2023-01-20 07:09:01 +01:00
parent 31fa792b5b
commit 625f0ef726
25 changed files with 1154 additions and 0 deletions

View File

@@ -100,3 +100,4 @@ add_subdirectory(qnx)
add_subdirectory(webassembly) add_subdirectory(webassembly)
add_subdirectory(mcusupport) add_subdirectory(mcusupport)
add_subdirectory(saferenderer) add_subdirectory(saferenderer)
add_subdirectory(copilot)

View File

@@ -0,0 +1,17 @@
add_qtc_plugin(Copilot
SKIP_TRANSLATION
PLUGIN_DEPENDS Core LanguageClient
SOURCES
authwidget.cpp authwidget.h
copilotplugin.cpp copilotplugin.h
copilotclient.cpp copilotclient.h
copilotsettings.cpp copilotsettings.h
copilotoptionspage.cpp copilotoptionspage.h
copilotoptionspagewidget.cpp copilotoptionspagewidget.h
documentwatcher.cpp documentwatcher.h
requests/getcompletions.h
requests/checkstatus.h
requests/signout.h
requests/signininitiate.h
requests/signinconfirm.h
)

View File

@@ -0,0 +1,19 @@
{
\"Name\" : \"Copilot\",
\"Version\" : \"$$QTCREATOR_VERSION\",
\"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
\"Experimental\" : true,
\"Vendor\" : \"The Qt Company Ltd\",
\"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
\"License\" : [ \"Commercial Usage\",
\"\",
\"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
\"\",
\"GNU General Public License Usage\",
\"\",
\"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
],
\"Description\" : \"Copilot support\",
\"Url\" : \"http://www.qt.io\",
$$dependencyList
}

View File

@@ -0,0 +1,163 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "authwidget.h"
#include "copilotclient.h"
#include "copilottr.h"
#include <utils/layoutbuilder.h>
#include <utils/stringutils.h>
#include <languageclient/languageclientmanager.h>
#include <QDesktopServices>
#include <QMessageBox>
using namespace LanguageClient;
namespace Copilot {
bool isCopilotClient(Client *client)
{
return dynamic_cast<Internal::CopilotClient *>(client) != nullptr;
}
Internal::CopilotClient *coPilotClient(Client *client)
{
return static_cast<Internal::CopilotClient *>(client);
}
Internal::CopilotClient *findClient()
{
return Internal::CopilotClient::instance();
}
AuthWidget::AuthWidget(QWidget *parent)
: QWidget(parent)
{
using namespace Utils::Layouting;
m_button = new QPushButton();
m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small);
m_statusLabel = new QLabel();
// clang-format off
Column {
Row {
m_button, m_progressIndicator, st
},
m_statusLabel
}.attachTo(this);
// clang-format on
setState("Checking status ...", true);
connect(LanguageClientManager::instance(),
&LanguageClientManager::clientAdded,
this,
&AuthWidget::onClientAdded);
connect(m_button, &QPushButton::clicked, this, [this]() {
if (m_status == Status::SignedIn)
signOut();
else if (m_status == Status::SignedOut)
signIn();
});
auto client = findClient();
if (client)
checkStatus(client);
}
void AuthWidget::setState(const QString &buttonText, bool working)
{
m_button->setText(buttonText);
m_progressIndicator->setVisible(working);
m_statusLabel->setVisible(!m_statusLabel->text().isEmpty());
m_button->setEnabled(!working);
}
void AuthWidget::onClientAdded(LanguageClient::Client *client)
{
if (isCopilotClient(client)) {
checkStatus(coPilotClient(client));
}
}
void AuthWidget::checkStatus(Internal::CopilotClient *client)
{
client->requestCheckStatus(false, [this](const CheckStatusRequest::Response &response) {
if (response.error()) {
setState("failed: " + response.error()->message(), false);
return;
}
const CheckStatusResponse result = *response.result();
if (result.user().isEmpty()) {
setState("Sign in", false);
m_status = Status::SignedOut;
return;
}
setState("Sign out " + result.user(), false);
m_status = Status::SignedIn;
});
}
void AuthWidget::signIn()
{
qCritical() << "Not implemented";
auto client = findClient();
QTC_ASSERT(client, return);
setState("Signing in ...", true);
client->requestSignInInitiate([this, client](const SignInInitiateRequest::Response &response) {
QTC_ASSERT(!response.error(), return);
Utils::setClipboardAndSelection(response.result()->userCode());
QDesktopServices::openUrl(QUrl(response.result()->verificationUri()));
m_statusLabel->setText(Tr::tr("A browser window will open, enter the code %1 when "
"asked.\nThe code has been copied to your clipboard.")
.arg(response.result()->userCode()));
m_statusLabel->setVisible(true);
client->requestSignInConfirm(response.result()->userCode(),
[this](const SignInConfirmRequest::Response &response) {
m_statusLabel->setText("");
if (response.error()) {
QMessageBox::critical(this,
Tr::tr("Login failed"),
Tr::tr(
"The login request failed: ")
+ response.error()->message());
setState("Sign in", false);
return;
}
setState("Sign Out " + response.result()->user(), false);
});
});
}
void AuthWidget::signOut()
{
auto client = findClient();
QTC_ASSERT(client, return);
setState("Signing out ...", true);
client->requestSignOut([this, client](const SignOutRequest::Response &response) {
QTC_ASSERT(!response.error(), return);
QTC_ASSERT(response.result()->status() == "NotSignedIn", return);
checkStatus(client);
});
}
} // namespace Copilot

View File

@@ -0,0 +1,44 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "copilotclient.h"
#include <utils/progressindicator.h>
#include <QLabel>
#include <QPushButton>
#include <QWidget>
namespace LanguageClient {
class Client;
}
namespace Copilot {
class AuthWidget : public QWidget
{
Q_OBJECT
enum class Status { SignedIn, SignedOut, Unknown };
public:
explicit AuthWidget(QWidget *parent = nullptr);
private:
void onClientAdded(LanguageClient::Client *client);
void setState(const QString &buttonText, bool working);
void checkStatus(Internal::CopilotClient *client);
void signIn();
void signOut();
private:
Status m_status = Status::Unknown;
QPushButton *m_button = nullptr;
QLabel *m_statusLabel = nullptr;
Utils::ProgressIndicator *m_progressIndicator = nullptr;
};
} // namespace Copilot

View File

@@ -0,0 +1,30 @@
import qbs 1.0
QtcPlugin {
name: "Copilot"
Depends { name: "Core" }
Depends { name: "Qt"; submodules: ["widgets", "xml", "network"] }
files: [
"authwidget.cpp",
"authwidget.h",
"copilotplugin.cpp",
"copilotplugin.h",
"copilotclient.cpp",
"copilotclient.h",
"copilotsettings.cpp",
"copilotsettings.h",
"copilotoptionspage.cpp",
"copilotoptionspage.h",
"copilotoptionspagewidget.cpp",
"copilotoptionspagewidget.h",
"documentwatcher.cpp",
"documentwatcher.h",
"requests/getcompletions.h",
"requests/checkstatus.h",
"requests/signout.h",
"requests/signininitiate.h",
"requests/signinconfirm.h",
]
}

View File

@@ -0,0 +1,133 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotclient.h"
#include "copilotsettings.h"
#include "documentwatcher.h"
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientsettings.h>
#include <coreplugin/editormanager/editormanager.h>
#include <utils/filepath.h>
using namespace Utils;
namespace Copilot::Internal {
static LanguageClient::BaseClientInterface *clientInterface()
{
const FilePath nodePath = CopilotSettings::instance().nodeJsPath.filePath();
const FilePath distPath = CopilotSettings::instance().distPath.filePath();
CommandLine cmd{nodePath, {distPath.toFSPathString()}};
const auto interface = new LanguageClient::StdIOClientInterface;
interface->setCommandLine(cmd);
return interface;
}
static CopilotClient *currentInstance = nullptr;
CopilotClient *CopilotClient::instance()
{
return currentInstance;
}
CopilotClient::CopilotClient()
: LanguageClient::Client(clientInterface())
{
setName("Copilot");
LanguageClient::LanguageFilter langFilter;
langFilter.filePattern = {"*"};
setSupportedLanguage(langFilter);
start();
connect(Core::EditorManager::instance(),
&Core::EditorManager::documentOpened,
this,
[this](Core::IDocument *document) {
TextEditor::TextDocument *textDocument = qobject_cast<TextEditor::TextDocument *>(
document);
if (!textDocument)
return;
openDocument(textDocument);
m_documentWatchers.emplace(textDocument->filePath(),
std::make_unique<DocumentWatcher>(this, textDocument));
});
connect(Core::EditorManager::instance(),
&Core::EditorManager::documentClosed,
this,
[this](Core::IDocument *document) {
auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
if (!textDocument)
return;
closeDocument(textDocument);
m_documentWatchers.erase(textDocument->filePath());
});
currentInstance = this;
}
void CopilotClient::requestCompletion(
const Utils::FilePath &path,
int version,
LanguageServerProtocol::Position position,
std::function<void(const GetCompletionRequest::Response &response)> callback)
{
GetCompletionRequest request{
{LanguageServerProtocol::TextDocumentIdentifier(hostPathToServerUri(path)),
version,
position}};
request.setResponseCallback(callback);
sendMessage(request);
}
void CopilotClient::requestCheckStatus(
bool localChecksOnly, std::function<void(const CheckStatusRequest::Response &response)> callback)
{
CheckStatusRequest request{localChecksOnly};
request.setResponseCallback(callback);
sendMessage(request);
}
void CopilotClient::requestSignOut(
std::function<void(const SignOutRequest::Response &response)> callback)
{
SignOutRequest request;
request.setResponseCallback(callback);
sendMessage(request);
}
void CopilotClient::requestSignInInitiate(
std::function<void(const SignInInitiateRequest::Response &response)> callback)
{
SignInInitiateRequest request;
request.setResponseCallback(callback);
sendMessage(request);
}
void CopilotClient::requestSignInConfirm(
const QString &userCode,
std::function<void(const SignInConfirmRequest::Response &response)> callback)
{
SignInConfirmRequest request(userCode);
request.setResponseCallback(callback);
sendMessage(request);
}
} // namespace Copilot::Internal

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "requests/checkstatus.h"
#include "requests/getcompletions.h"
#include "requests/signinconfirm.h"
#include "requests/signininitiate.h"
#include "requests/signout.h"
#include <languageclient/client.h>
#include <utils/filepath.h>
#include <QHash>
#include <QTemporaryDir>
namespace Copilot::Internal {
class DocumentWatcher;
class CopilotClient : public LanguageClient::Client
{
public:
explicit CopilotClient();
static CopilotClient *instance();
void requestCompletion(
const Utils::FilePath &path,
int version,
LanguageServerProtocol::Position position,
std::function<void(const GetCompletionRequest::Response &response)> callback);
void requestCheckStatus(
bool localChecksOnly,
std::function<void(const CheckStatusRequest::Response &response)> callback);
void requestSignOut(std::function<void(const SignOutRequest::Response &response)> callback);
void requestSignInInitiate(
std::function<void(const SignInInitiateRequest::Response &response)> callback);
void requestSignInConfirm(
const QString &userCode,
std::function<void(const SignInConfirmRequest::Response &response)> callback);
private:
std::map<Utils::FilePath, std::unique_ptr<DocumentWatcher>> m_documentWatchers;
};
} // namespace Copilot::Internal

View File

@@ -0,0 +1,46 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotoptionspage.h"
#include "copilotoptionspagewidget.h"
#include "copilotsettings.h"
#include <coreplugin/icore.h>
namespace Copilot {
CopilotOptionsPage::CopilotOptionsPage()
{
setId("Copilot.General");
setDisplayName("Copilot");
setCategory("ZY.Copilot");
setDisplayCategory("Copilot");
setCategoryIconPath(":/languageclient/images/settingscategory_languageclient.png");
}
CopilotOptionsPage::~CopilotOptionsPage() {}
void CopilotOptionsPage::init() {}
QWidget *CopilotOptionsPage::widget()
{
return new CopilotOptionsPageWidget();
}
void CopilotOptionsPage::apply()
{
CopilotSettings::instance().apply();
CopilotSettings::instance().writeSettings(Core::ICore::settings());
}
void CopilotOptionsPage::finish() {}
CopilotOptionsPage &CopilotOptionsPage::instance()
{
static CopilotOptionsPage settingsPage;
return settingsPage;
}
} // namespace Copilot

View File

@@ -0,0 +1,25 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <coreplugin/dialogs/ioptionspage.h>
namespace Copilot {
class CopilotOptionsPage : public Core::IOptionsPage
{
public:
CopilotOptionsPage();
~CopilotOptionsPage() override;
static CopilotOptionsPage &instance();
void init();
QWidget *widget() override;
void apply() override;
void finish() override;
};
} // namespace Copilot

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotoptionspagewidget.h"
#include "authwidget.h"
#include "copilotsettings.h"
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
using namespace Utils;
using namespace LanguageClient;
namespace Copilot {
CopilotOptionsPageWidget::CopilotOptionsPageWidget(QWidget *parent)
: QWidget(parent)
{
using namespace Layouting;
auto authWdgt = new AuthWidget();
// clang-format off
Column {
authWdgt, br,
CopilotSettings::instance().nodeJsPath, br,
CopilotSettings::instance().distPath, br,
st
}.attachTo(this);
// clang-format on
}
CopilotOptionsPageWidget::~CopilotOptionsPageWidget() = default;
} // namespace Copilot

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <QWidget>
namespace Copilot {
class CopilotOptionsPageWidget : public QWidget
{
Q_OBJECT
public:
CopilotOptionsPageWidget(QWidget *parent = nullptr);
~CopilotOptionsPageWidget() override;
};
} // namespace Copilot

View File

@@ -0,0 +1,37 @@
// 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 "copilotplugin.h"
#include "copilotclient.h"
#include "copilotoptionspage.h"
#include "copilotsettings.h"
#include <coreplugin/icore.h>
using namespace Utils;
namespace Copilot {
namespace Internal {
void CopilotPlugin::initialize()
{
CopilotSettings::instance().readSettings(Core::ICore::settings());
m_client = new CopilotClient();
connect(&CopilotSettings::instance(), &CopilotSettings::applied, this, [this]() {
if (m_client)
m_client->shutdown();
m_client = nullptr;
m_client = new CopilotClient();
});
}
void CopilotPlugin::extensionsInitialized()
{
CopilotOptionsPage::instance().init();
}
} // namespace Internal
} // namespace Copilot

View File

@@ -0,0 +1,30 @@
// 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 "copilotclient.h"
#include <extensionsystem/iplugin.h>
namespace Copilot {
namespace Internal {
class CopilotPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Copilot.json")
public:
CopilotPlugin() {}
~CopilotPlugin() override {}
void initialize() override;
void extensionsInitialized() override;
private:
CopilotClient *m_client;
};
} // namespace Internal
} // namespace Copilot

View File

@@ -0,0 +1,65 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "copilotsettings.h"
#include "copilottr.h"
#include <utils/algorithm.h>
#include <utils/environment.h>
using namespace Utils;
namespace Copilot {
CopilotSettings &CopilotSettings::instance()
{
static CopilotSettings settings;
return settings;
}
CopilotSettings::CopilotSettings()
{
setAutoApply(false);
const FilePath nodeFromPath = FilePath("node").searchInPath();
const FilePaths searchDirs
= {FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
FilePath::fromUserInput(
"~/.config/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
FilePath::fromUserInput(
"~/vimfiles/pack/github/start/copilot.vim/copilot/dist/agent.js"),
FilePath::fromUserInput(
"~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js")};
const FilePath distFromVim = Utils::findOrDefault(searchDirs, [](const FilePath &fp) {
return fp.exists();
});
nodeJsPath.setExpectedKind(PathChooser::ExistingCommand);
nodeJsPath.setDefaultFilePath(nodeFromPath);
nodeJsPath.setSettingsKey("Copilot.NodeJsPath");
nodeJsPath.setDisplayStyle(StringAspect::PathChooserDisplay);
nodeJsPath.setLabelText(Tr::tr("Node.js path:"));
nodeJsPath.setHistoryCompleter("Copilot.NodePath.History");
nodeJsPath.setDisplayName(Tr::tr("Node.js Path"));
nodeJsPath.setToolTip(
Tr::tr("Select path to node.js executable. See https://nodejs.org/de/download/"
"for installation instructions."));
distPath.setExpectedKind(PathChooser::File);
distPath.setDefaultFilePath(distFromVim);
distPath.setSettingsKey("Copilot.DistPath");
distPath.setDisplayStyle(StringAspect::PathChooserDisplay);
distPath.setLabelText(Tr::tr("Path to agent.js:"));
distPath.setToolTip(Tr::tr(
"Select path to agent.js in copilot neovim plugin. See "
"https://github.com/github/copilot.vim#getting-started for installation instructions."));
distPath.setHistoryCompleter("Copilot.DistPath.History");
distPath.setDisplayName(Tr::tr("Agent.js path"));
registerAspect(&nodeJsPath);
registerAspect(&distPath);
}
} // namespace Copilot

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/aspects.h>
namespace Copilot {
class CopilotSettings : public Utils::AspectContainer
{
public:
CopilotSettings();
static CopilotSettings &instance();
Utils::StringAspect nodeJsPath;
Utils::StringAspect distPath;
};
} // namespace Copilot

View File

@@ -0,0 +1,15 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QCoreApplication>
namespace Copilot {
struct Tr
{
Q_DECLARE_TR_FUNCTIONS(::Copilot)
};
} // namespace Copilot

View File

@@ -0,0 +1,92 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "documentwatcher.h"
#include <coreplugin/editormanager/editormanager.h>
#include <texteditor/texteditor.h>
#include <QJsonDocument>
using namespace LanguageServerProtocol;
using namespace TextEditor;
namespace Copilot::Internal {
DocumentWatcher::DocumentWatcher(CopilotClient *client, TextDocument *textDocument)
: m_client(client)
, m_textDocument(textDocument)
{
m_lastContentSize = m_textDocument->document()->characterCount(); //toPlainText().size();
m_debounceTimer.setInterval(500);
m_debounceTimer.setSingleShot(true);
connect(textDocument, &TextDocument::contentsChanged, this, [this]() {
if (!m_isEditing) {
const int newSize = m_textDocument->document()->characterCount();
if (m_lastContentSize < newSize) {
m_debounceTimer.start();
}
m_lastContentSize = newSize;
}
});
connect(&m_debounceTimer, &QTimer::timeout, this, [this]() { getSuggestion(); });
}
void DocumentWatcher::getSuggestion()
{
auto editor = Core::EditorManager::instance()->activateEditorForDocument(m_textDocument);
auto textEditorWidget = qobject_cast<TextEditor::TextEditorWidget *>(editor->widget());
if (!editor || !textEditorWidget)
return;
auto cursor = textEditorWidget->multiTextCursor();
if (cursor.hasMultipleCursors() || cursor.hasSelection())
return;
const int currentCursorPos = cursor.cursors().first().position();
m_client->requestCompletion(
m_textDocument->filePath(),
m_client->documentVersion(m_textDocument->filePath()),
Position(editor->currentLine() - 1, editor->currentColumn() - 1),
[this, textEditorWidget, currentCursorPos](const GetCompletionRequest::Response &response) {
if (response.error()) {
qDebug() << "ERROR:" << *response.error();
return;
}
const std::optional<GetCompletionResponse> result = response.result();
QTC_ASSERT(result, return);
const auto list = result->completions().toList();
if (list.isEmpty())
return;
auto cursor = textEditorWidget->multiTextCursor();
if (cursor.hasMultipleCursors() || cursor.hasSelection())
return;
if (cursor.cursors().first().position() != currentCursorPos)
return;
const auto firstCompletion = list.first();
const QString content = firstCompletion.text().mid(
firstCompletion.position().character());
m_isEditing = true;
textEditorWidget->insertSuggestion(content);
m_isEditing = false;
/*
m_isEditing = true;
const auto &block = m_textDocument->document()->findBlockByLineNumber(
firstCompletion.position().line());
m_textDocument->insertSuggestion(content, block);
m_isEditing = false;
*/
});
}
} // namespace Copilot::Internal

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "copilotclient.h"
#include <texteditor/textdocument.h>
#include <QTimer>
namespace Copilot::Internal {
class DocumentWatcher : public QObject
{
Q_OBJECT
public:
explicit DocumentWatcher(CopilotClient *client, TextEditor::TextDocument *textDocument);
void getSuggestion();
private:
CopilotClient *m_client;
TextEditor::TextDocument *m_textDocument;
QTimer m_debounceTimer;
bool m_isEditing = false;
int m_lastContentSize = 0;
};
} // namespace Copilot::Internal

View File

@@ -0,0 +1,54 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
class CheckStatusParams : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t optionsKey[] = u"options";
static constexpr char16_t localChecksOnlyKey[] = u"options";
public:
using JsonObject::JsonObject;
CheckStatusParams(bool localChecksOnly = false) { setLocalChecksOnly(localChecksOnly); }
void setLocalChecksOnly(bool localChecksOnly)
{
QJsonObject options;
options.insert(localChecksOnlyKey, localChecksOnly);
setOptions(options);
}
void setOptions(QJsonObject options) { insert(optionsKey, options); }
};
class CheckStatusResponse : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t userKey[] = u"user";
static constexpr char16_t statusKey[] = u"status";
public:
using JsonObject::JsonObject;
QString status() const { return typedValue<QString>(statusKey); }
QString user() const { return typedValue<QString>(userKey); }
};
class CheckStatusRequest
: public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, CheckStatusParams>
{
public:
explicit CheckStatusRequest(const CheckStatusParams &params)
: Request(methodName, params)
{}
using Request::Request;
constexpr static const char methodName[] = "checkStatus";
};
} // namespace Copilot

View File

@@ -0,0 +1,120 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <languageserverprotocol/jsonkeys.h>
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
class Completion : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t displayTextKey[] = u"displayText";
static constexpr char16_t uuidKey[] = u"uuid";
public:
using JsonObject::JsonObject;
QString displayText() const { return typedValue<QString>(displayTextKey); }
LanguageServerProtocol::Position position() const
{
return typedValue<LanguageServerProtocol::Position>(LanguageServerProtocol::positionKey);
}
LanguageServerProtocol::Range range() const
{
return typedValue<LanguageServerProtocol::Range>(LanguageServerProtocol::rangeKey);
}
QString text() const { return typedValue<QString>(LanguageServerProtocol::textKey); }
QString uuid() const { return typedValue<QString>(uuidKey); }
bool isValid() const override
{
return contains(LanguageServerProtocol::textKey)
&& contains(LanguageServerProtocol::rangeKey)
&& contains(LanguageServerProtocol::positionKey);
}
};
class GetCompletionParams : public LanguageServerProtocol::JsonObject
{
public:
static constexpr char16_t docKey[] = u"doc";
GetCompletionParams();
GetCompletionParams(const LanguageServerProtocol::TextDocumentIdentifier &document,
int version,
const LanguageServerProtocol::Position &position)
{
setTextDocument(document);
setVersion(version);
setPosition(position);
}
using JsonObject::JsonObject;
// The text document.
LanguageServerProtocol::TextDocumentIdentifier textDocument() const
{
return typedValue<LanguageServerProtocol::TextDocumentIdentifier>(docKey);
}
void setTextDocument(const LanguageServerProtocol::TextDocumentIdentifier &id)
{
insert(docKey, id);
}
// The position inside the text document.
LanguageServerProtocol::Position position() const
{
return LanguageServerProtocol::fromJsonValue<LanguageServerProtocol::Position>(
value(docKey).toObject().value(LanguageServerProtocol::positionKey));
}
void setPosition(const LanguageServerProtocol::Position &position)
{
QJsonObject result = value(docKey).toObject();
result[LanguageServerProtocol::positionKey] = (QJsonObject) position;
insert(docKey, result);
}
// The version
int version() const { return typedValue<int>(LanguageServerProtocol::versionKey); }
void setVersion(int version)
{
QJsonObject result = value(docKey).toObject();
result[LanguageServerProtocol::versionKey] = version;
insert(docKey, result);
}
bool isValid() const override
{
return contains(docKey)
&& value(docKey).toObject().contains(LanguageServerProtocol::positionKey)
&& value(docKey).toObject().contains(LanguageServerProtocol::versionKey);
}
};
class GetCompletionResponse : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t completionKey[] = u"completions";
public:
using JsonObject::JsonObject;
LanguageServerProtocol::LanguageClientArray<Completion> completions() const
{
return clientArray<Completion>(completionKey);
}
};
class GetCompletionRequest
: public LanguageServerProtocol::Request<GetCompletionResponse, std::nullptr_t, GetCompletionParams>
{
public:
explicit GetCompletionRequest(const GetCompletionParams &params)
: Request(methodName, params)
{}
using Request::Request;
constexpr static const char methodName[] = "getCompletionsCycling";
};
} // namespace Copilot

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "checkstatus.h"
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
class SignInConfirmParams : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t userCodeKey[] = u"userCode";
public:
using JsonObject::JsonObject;
SignInConfirmParams(const QString &userCode) { setUserCode(userCode); }
void setUserCode(const QString &userCode) { insert(userCodeKey, userCode); }
};
class SignInConfirmRequest
: public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, SignInConfirmParams>
{
public:
explicit SignInConfirmRequest(const QString &userCode)
: Request(methodName, {userCode})
{}
using Request::Request;
constexpr static const char methodName[] = "signInConfirm";
};
} // namespace Copilot

View File

@@ -0,0 +1,43 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
using SignInInitiateParams = LanguageServerProtocol::JsonObject;
class SignInInitiateResponse : public LanguageServerProtocol::JsonObject
{
static constexpr char16_t verificationUriKey[] = u"verificationUri";
static constexpr char16_t userCodeKey[] = u"userCode";
public:
using JsonObject::JsonObject;
public:
QString verificationUri() const { return typedValue<QString>(verificationUriKey); }
QString userCode() const { return typedValue<QString>(userCodeKey); }
};
class SignInInitiateRequest : public LanguageServerProtocol::Request<SignInInitiateResponse,
std::nullptr_t,
SignInInitiateParams>
{
public:
explicit SignInInitiateRequest()
: Request(methodName, {})
{}
using Request::Request;
constexpr static const char methodName[] = "signInInitiate";
};
} // namespace Copilot

View File

@@ -0,0 +1,26 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include "checkstatus.h"
#include <languageserverprotocol/jsonrpcmessages.h>
#include <languageserverprotocol/lsptypes.h>
namespace Copilot {
using SignOutParams = LanguageServerProtocol::JsonObject;
class SignOutRequest
: public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, SignOutParams>
{
public:
explicit SignOutRequest()
: Request(methodName, {})
{}
using Request::Request;
constexpr static const char methodName[] = "signOut";
};
} // namespace Copilot

View File

@@ -85,5 +85,6 @@ Project {
"vcsbase/vcsbase.qbs", "vcsbase/vcsbase.qbs",
"webassembly/webassembly.qbs", "webassembly/webassembly.qbs",
"welcome/welcome.qbs" "welcome/welcome.qbs"
"copilot/copilot.qbs"
].concat(project.additionalPlugins) ].concat(project.additionalPlugins)
} }