2018-07-13 12:33:46 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2018 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the 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. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file 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 file. 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.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
#include "client.h"
|
2019-01-25 09:48:44 +01:00
|
|
|
|
2019-01-31 08:46:23 +01:00
|
|
|
#include "languageclientinterface.h"
|
2018-07-13 12:33:46 +02:00
|
|
|
#include "languageclientmanager.h"
|
2019-01-31 08:46:23 +01:00
|
|
|
#include "languageclientutils.h"
|
2019-06-12 12:55:06 +02:00
|
|
|
#include "semantichighlightsupport.h"
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2020-09-09 08:15:11 +02:00
|
|
|
#include <coreplugin/editormanager/documentmodel.h>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/idocument.h>
|
|
|
|
|
#include <coreplugin/messagemanager.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <coreplugin/progressmanager/progressmanager.h>
|
|
|
|
|
|
2021-01-20 12:46:07 +01:00
|
|
|
#include <languageserverprotocol/completion.h>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <languageserverprotocol/diagnostics.h>
|
|
|
|
|
#include <languageserverprotocol/languagefeatures.h>
|
|
|
|
|
#include <languageserverprotocol/messages.h>
|
2021-01-20 12:46:07 +01:00
|
|
|
#include <languageserverprotocol/servercapabilities.h>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <languageserverprotocol/workspace.h>
|
2021-02-02 09:50:56 +01:00
|
|
|
#include <languageserverprotocol/progresssupport.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2019-05-16 11:09:55 +02:00
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2019-05-16 11:09:55 +02:00
|
|
|
#include <texteditor/codeassist/documentcontentcompletion.h>
|
2020-03-26 09:21:57 +01:00
|
|
|
#include <texteditor/codeassist/iassistprocessor.h>
|
2020-05-11 08:41:57 +02:00
|
|
|
#include <texteditor/ioutlinewidget.h>
|
2019-06-12 12:55:06 +02:00
|
|
|
#include <texteditor/syntaxhighlighter.h>
|
2020-01-08 12:03:54 +01:00
|
|
|
#include <texteditor/tabsettings.h>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
#include <texteditor/texteditor.h>
|
2020-05-11 08:41:57 +02:00
|
|
|
#include <texteditor/texteditoractionhandler.h>
|
2019-06-12 12:55:06 +02:00
|
|
|
#include <texteditor/texteditorsettings.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <utils/mimetypes/mimedatabase.h>
|
2018-09-10 15:15:37 +02:00
|
|
|
#include <utils/qtcprocess.h>
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QLoggingCategory>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#include <QPointer>
|
2018-09-12 10:24:02 +02:00
|
|
|
#include <QPushButton>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <QTextBlock>
|
|
|
|
|
#include <QTextCursor>
|
|
|
|
|
#include <QTextDocument>
|
2018-09-19 09:36:32 +02:00
|
|
|
#include <QTimer>
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
using namespace LanguageServerProtocol;
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace LanguageClient {
|
|
|
|
|
|
2018-10-12 09:33:30 +03:00
|
|
|
static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg);
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
Client::Client(BaseClientInterface *clientInterface)
|
2020-06-26 13:59:38 +02:00
|
|
|
: m_id(Utils::Id::fromString(QUuid::createUuid().toString()))
|
2019-01-31 08:46:23 +01:00
|
|
|
, m_clientInterface(clientInterface)
|
2021-06-22 15:28:35 +02:00
|
|
|
, m_diagnosticManager(this)
|
2019-04-04 14:36:28 +02:00
|
|
|
, m_documentSymbolCache(this)
|
2019-05-15 11:19:31 +02:00
|
|
|
, m_hoverHandler(this)
|
2020-05-06 10:26:31 +02:00
|
|
|
, m_symbolSupport(this)
|
2021-08-09 14:45:57 +02:00
|
|
|
, m_tokenSupport(this)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-11-11 14:31:22 +01:00
|
|
|
using namespace ProjectExplorer;
|
2019-09-10 08:03:36 +02:00
|
|
|
m_clientProviders.completionAssistProvider = new LanguageClientCompletionAssistProvider(this);
|
|
|
|
|
m_clientProviders.functionHintProvider = new FunctionHintAssistProvider(this);
|
|
|
|
|
m_clientProviders.quickFixAssistProvider = new LanguageClientQuickFixProvider(this);
|
|
|
|
|
|
2020-06-17 14:15:13 +02:00
|
|
|
m_documentUpdateTimer.setSingleShot(true);
|
|
|
|
|
m_documentUpdateTimer.setInterval(500);
|
2021-09-14 13:16:43 +02:00
|
|
|
connect(&m_documentUpdateTimer, &QTimer::timeout, this,
|
2021-09-14 17:00:16 +02:00
|
|
|
[this] { sendPostponedDocumentUpdates(Schedule::Now); });
|
2021-11-11 14:31:22 +01:00
|
|
|
connect(SessionManager::instance(), &SessionManager::projectRemoved,
|
|
|
|
|
this, &Client::projectClosed);
|
2020-06-17 14:15:13 +02:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(),
|
|
|
|
|
&JsonRpcMessageHandler::parseContent);
|
2019-01-31 08:46:23 +01:00
|
|
|
QTC_ASSERT(clientInterface, return);
|
2019-01-31 12:15:43 +01:00
|
|
|
connect(clientInterface, &BaseClientInterface::messageReceived, this, &Client::handleMessage);
|
|
|
|
|
connect(clientInterface, &BaseClientInterface::error, this, &Client::setError);
|
|
|
|
|
connect(clientInterface, &BaseClientInterface::finished, this, &Client::finished);
|
2022-02-11 14:50:57 +01:00
|
|
|
connect(Core::EditorManager::instance(),
|
|
|
|
|
&Core::EditorManager::documentClosed,
|
|
|
|
|
this,
|
|
|
|
|
&Client::documentClosed);
|
2021-02-25 12:54:14 +01:00
|
|
|
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.setTokenTypesMap(SemanticTokens::defaultTokenTypesMap());
|
|
|
|
|
m_tokenSupport.setTokenModifiersMap(SemanticTokens::defaultTokenModifiersMap());
|
2022-02-22 07:42:54 +01:00
|
|
|
|
|
|
|
|
m_shutdownTimer.setInterval(20 /*seconds*/ * 1000);
|
|
|
|
|
connect(&m_shutdownTimer, &QTimer::timeout, this, [this] {
|
|
|
|
|
LanguageClientManager::deleteClient(this);
|
|
|
|
|
});
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-23 09:22:11 +01:00
|
|
|
QString Client::name() const
|
|
|
|
|
{
|
|
|
|
|
if (m_project && !m_project->displayName().isEmpty())
|
|
|
|
|
return tr("%1 for %2").arg(m_displayName, m_project->displayName());
|
|
|
|
|
return m_displayName;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 14:31:20 +02:00
|
|
|
static void updateEditorToolBar(QList<TextEditor::TextDocument *> documents)
|
2019-03-26 13:48:06 +01:00
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
for (TextEditor::TextDocument *document : documents) {
|
|
|
|
|
for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document))
|
|
|
|
|
updateEditorToolBar(editor);
|
|
|
|
|
}
|
2019-03-26 13:48:06 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
Client::~Client()
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-01-25 09:48:44 +01:00
|
|
|
using namespace TextEditor;
|
2018-10-16 07:00:48 +02:00
|
|
|
// FIXME: instead of replacing the completion provider in the text document store the
|
|
|
|
|
// completion provider as a prioritised list in the text document
|
2021-01-22 14:26:11 +01:00
|
|
|
// temporary container needed since m_resetAssistProvider is changed in resetAssistProviders
|
|
|
|
|
for (TextDocument *document : m_resetAssistProvider.keys())
|
|
|
|
|
resetAssistProviders(document);
|
2020-12-08 15:41:46 +01:00
|
|
|
const QList<Core::IEditor *> &editors = Core::DocumentModel::editorsForOpenedDocuments();
|
|
|
|
|
for (Core::IEditor *editor : editors) {
|
2019-01-25 09:48:44 +01:00
|
|
|
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor)) {
|
|
|
|
|
TextEditorWidget *widget = textEditor->editorWidget();
|
|
|
|
|
widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id()));
|
2019-05-15 11:19:31 +02:00
|
|
|
widget->removeHoverHandler(&m_hoverHandler);
|
2019-01-25 09:48:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
2020-12-08 15:41:46 +01:00
|
|
|
for (IAssistProcessor *processor : qAsConst(m_runningAssistProcessors))
|
2020-03-26 09:21:57 +01:00
|
|
|
processor->setAsyncProposalAvailable(nullptr);
|
2021-03-12 13:49:13 +01:00
|
|
|
qDeleteAll(m_documentHighlightsTimer);
|
|
|
|
|
m_documentHighlightsTimer.clear();
|
2019-04-02 10:49:23 +02:00
|
|
|
updateEditorToolBar(m_openedDocument.keys());
|
2021-02-19 12:31:33 +01:00
|
|
|
// do not handle messages while shutting down
|
|
|
|
|
disconnect(m_clientInterface.data(), &BaseClientInterface::messageReceived,
|
|
|
|
|
this, &Client::handleMessage);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-03-15 14:53:36 +01:00
|
|
|
static ClientCapabilities generateClientCapabilities()
|
|
|
|
|
{
|
|
|
|
|
ClientCapabilities capabilities;
|
|
|
|
|
WorkspaceClientCapabilities workspaceCapabilities;
|
|
|
|
|
workspaceCapabilities.setWorkspaceFolders(true);
|
|
|
|
|
workspaceCapabilities.setApplyEdit(true);
|
|
|
|
|
DynamicRegistrationCapabilities allowDynamicRegistration;
|
|
|
|
|
allowDynamicRegistration.setDynamicRegistration(true);
|
|
|
|
|
workspaceCapabilities.setDidChangeConfiguration(allowDynamicRegistration);
|
|
|
|
|
workspaceCapabilities.setExecuteCommand(allowDynamicRegistration);
|
2021-02-25 12:54:14 +01:00
|
|
|
workspaceCapabilities.setConfiguration(true);
|
2021-11-02 14:21:50 +01:00
|
|
|
SemanticTokensWorkspaceClientCapabilities semanticTokensWorkspaceClientCapabilities;
|
|
|
|
|
semanticTokensWorkspaceClientCapabilities.setRefreshSupport(true);
|
|
|
|
|
workspaceCapabilities.setSemanticTokens(semanticTokensWorkspaceClientCapabilities);
|
2019-03-15 14:53:36 +01:00
|
|
|
capabilities.setWorkspace(workspaceCapabilities);
|
|
|
|
|
|
|
|
|
|
TextDocumentClientCapabilities documentCapabilities;
|
|
|
|
|
TextDocumentClientCapabilities::SynchronizationCapabilities syncCapabilities;
|
|
|
|
|
syncCapabilities.setDynamicRegistration(true);
|
|
|
|
|
syncCapabilities.setWillSave(true);
|
|
|
|
|
syncCapabilities.setWillSaveWaitUntil(false);
|
|
|
|
|
syncCapabilities.setDidSave(true);
|
|
|
|
|
documentCapabilities.setSynchronization(syncCapabilities);
|
|
|
|
|
|
|
|
|
|
SymbolCapabilities symbolCapabilities;
|
|
|
|
|
SymbolCapabilities::SymbolKindCapabilities symbolKindCapabilities;
|
|
|
|
|
symbolKindCapabilities.setValueSet(
|
|
|
|
|
{SymbolKind::File, SymbolKind::Module, SymbolKind::Namespace,
|
|
|
|
|
SymbolKind::Package, SymbolKind::Class, SymbolKind::Method,
|
|
|
|
|
SymbolKind::Property, SymbolKind::Field, SymbolKind::Constructor,
|
|
|
|
|
SymbolKind::Enum, SymbolKind::Interface, SymbolKind::Function,
|
|
|
|
|
SymbolKind::Variable, SymbolKind::Constant, SymbolKind::String,
|
|
|
|
|
SymbolKind::Number, SymbolKind::Boolean, SymbolKind::Array,
|
|
|
|
|
SymbolKind::Object, SymbolKind::Key, SymbolKind::Null,
|
|
|
|
|
SymbolKind::EnumMember, SymbolKind::Struct, SymbolKind::Event,
|
|
|
|
|
SymbolKind::Operator, SymbolKind::TypeParameter});
|
|
|
|
|
symbolCapabilities.setSymbolKind(symbolKindCapabilities);
|
2021-06-01 12:57:37 +02:00
|
|
|
symbolCapabilities.setHierarchicalDocumentSymbolSupport(true);
|
2019-03-15 14:53:36 +01:00
|
|
|
documentCapabilities.setDocumentSymbol(symbolCapabilities);
|
|
|
|
|
|
|
|
|
|
TextDocumentClientCapabilities::CompletionCapabilities completionCapabilities;
|
|
|
|
|
completionCapabilities.setDynamicRegistration(true);
|
|
|
|
|
TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities
|
|
|
|
|
completionItemKindCapabilities;
|
|
|
|
|
completionItemKindCapabilities.setValueSet(
|
|
|
|
|
{CompletionItemKind::Text, CompletionItemKind::Method,
|
|
|
|
|
CompletionItemKind::Function, CompletionItemKind::Constructor,
|
|
|
|
|
CompletionItemKind::Field, CompletionItemKind::Variable,
|
|
|
|
|
CompletionItemKind::Class, CompletionItemKind::Interface,
|
|
|
|
|
CompletionItemKind::Module, CompletionItemKind::Property,
|
|
|
|
|
CompletionItemKind::Unit, CompletionItemKind::Value,
|
|
|
|
|
CompletionItemKind::Enum, CompletionItemKind::Keyword,
|
|
|
|
|
CompletionItemKind::Snippet, CompletionItemKind::Color,
|
|
|
|
|
CompletionItemKind::File, CompletionItemKind::Reference,
|
|
|
|
|
CompletionItemKind::Folder, CompletionItemKind::EnumMember,
|
|
|
|
|
CompletionItemKind::Constant, CompletionItemKind::Struct,
|
|
|
|
|
CompletionItemKind::Event, CompletionItemKind::Operator,
|
|
|
|
|
CompletionItemKind::TypeParameter});
|
|
|
|
|
completionCapabilities.setCompletionItemKind(completionItemKindCapabilities);
|
2019-05-06 08:19:22 +02:00
|
|
|
TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemCapbilities
|
|
|
|
|
completionItemCapbilities;
|
2021-05-11 13:37:52 +02:00
|
|
|
completionItemCapbilities.setSnippetSupport(true);
|
2019-05-06 08:25:30 +02:00
|
|
|
completionItemCapbilities.setCommitCharacterSupport(true);
|
2019-05-06 08:19:22 +02:00
|
|
|
completionCapabilities.setCompletionItem(completionItemCapbilities);
|
2019-03-15 14:53:36 +01:00
|
|
|
documentCapabilities.setCompletion(completionCapabilities);
|
|
|
|
|
|
|
|
|
|
TextDocumentClientCapabilities::CodeActionCapabilities codeActionCapabilities;
|
|
|
|
|
TextDocumentClientCapabilities::CodeActionCapabilities::CodeActionLiteralSupport literalSupport;
|
|
|
|
|
literalSupport.setCodeActionKind(
|
|
|
|
|
TextDocumentClientCapabilities::CodeActionCapabilities::CodeActionLiteralSupport::
|
|
|
|
|
CodeActionKind(QList<QString>{"*"}));
|
|
|
|
|
codeActionCapabilities.setCodeActionLiteralSupport(literalSupport);
|
|
|
|
|
documentCapabilities.setCodeAction(codeActionCapabilities);
|
2019-05-15 11:19:31 +02:00
|
|
|
|
|
|
|
|
TextDocumentClientCapabilities::HoverCapabilities hover;
|
2019-11-04 17:14:25 +01:00
|
|
|
hover.setContentFormat({MarkupKind::markdown, MarkupKind::plaintext});
|
2019-05-15 11:19:31 +02:00
|
|
|
hover.setDynamicRegistration(true);
|
|
|
|
|
documentCapabilities.setHover(hover);
|
|
|
|
|
|
2020-05-11 08:41:57 +02:00
|
|
|
TextDocumentClientCapabilities::RenameClientCapabilities rename;
|
|
|
|
|
rename.setPrepareSupport(true);
|
|
|
|
|
rename.setDynamicRegistration(true);
|
|
|
|
|
documentCapabilities.setRename(rename);
|
|
|
|
|
|
2021-01-22 10:12:16 +01:00
|
|
|
TextDocumentClientCapabilities::SignatureHelpCapabilities signatureHelp;
|
|
|
|
|
signatureHelp.setDynamicRegistration(true);
|
|
|
|
|
TextDocumentClientCapabilities::SignatureHelpCapabilities::SignatureInformationCapabilities info;
|
|
|
|
|
info.setDocumentationFormat({MarkupKind::markdown, MarkupKind::plaintext});
|
2021-09-30 07:23:23 +02:00
|
|
|
info.setActiveParameterSupport(true);
|
2021-01-22 10:12:16 +01:00
|
|
|
signatureHelp.setSignatureInformation(info);
|
|
|
|
|
documentCapabilities.setSignatureHelp(signatureHelp);
|
|
|
|
|
|
2019-03-26 09:29:55 +01:00
|
|
|
documentCapabilities.setReferences(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setDocumentHighlight(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setDefinition(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setTypeDefinition(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setImplementation(allowDynamicRegistration);
|
2021-01-26 14:00:29 +01:00
|
|
|
documentCapabilities.setFormatting(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setRangeFormatting(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setOnTypeFormatting(allowDynamicRegistration);
|
2021-02-25 12:54:14 +01:00
|
|
|
SemanticTokensClientCapabilities tokens;
|
|
|
|
|
tokens.setDynamicRegistration(true);
|
|
|
|
|
FullSemanticTokenOptions tokenOptions;
|
|
|
|
|
tokenOptions.setDelta(true);
|
|
|
|
|
SemanticTokensClientCapabilities::Requests tokenRequests;
|
|
|
|
|
tokenRequests.setFull(tokenOptions);
|
|
|
|
|
tokens.setRequests(tokenRequests);
|
|
|
|
|
tokens.setTokenTypes({"type",
|
|
|
|
|
"class",
|
|
|
|
|
"enumMember",
|
|
|
|
|
"typeParameter",
|
|
|
|
|
"parameter",
|
|
|
|
|
"variable",
|
|
|
|
|
"function",
|
|
|
|
|
"macro",
|
|
|
|
|
"keyword",
|
|
|
|
|
"comment",
|
|
|
|
|
"string",
|
|
|
|
|
"number",
|
|
|
|
|
"operator"});
|
|
|
|
|
tokens.setTokenModifiers({"declaration", "definition"});
|
|
|
|
|
tokens.setFormats({"relative"});
|
|
|
|
|
documentCapabilities.setSemanticTokens(tokens);
|
2019-03-15 14:53:36 +01:00
|
|
|
capabilities.setTextDocument(documentCapabilities);
|
|
|
|
|
|
2021-02-02 09:50:56 +01:00
|
|
|
WindowClientClientCapabilities window;
|
|
|
|
|
window.setWorkDoneProgress(true);
|
|
|
|
|
capabilities.setWindow(window);
|
|
|
|
|
|
2019-03-15 14:53:36 +01:00
|
|
|
return capabilities;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::initialize()
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
using namespace ProjectExplorer;
|
2019-01-31 08:46:23 +01:00
|
|
|
QTC_ASSERT(m_clientInterface, return);
|
2018-07-13 12:33:46 +02:00
|
|
|
QTC_ASSERT(m_state == Uninitialized, return);
|
|
|
|
|
qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName;
|
2020-07-14 14:31:20 +02:00
|
|
|
InitializeParams params;
|
2021-02-23 13:51:41 +01:00
|
|
|
params.setCapabilities(m_clientCapabilities);
|
2020-05-09 02:51:19 +02:00
|
|
|
params.setInitializationOptions(m_initializationOptions);
|
2019-03-15 11:25:48 +01:00
|
|
|
if (m_project) {
|
2019-09-11 14:34:20 +02:00
|
|
|
params.setRootUri(DocumentUri::fromFilePath(m_project->projectDirectory()));
|
2020-08-12 13:07:14 +02:00
|
|
|
params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro) {
|
|
|
|
|
return WorkSpaceFolder(DocumentUri::fromFilePath(pro->projectDirectory()),
|
|
|
|
|
pro->displayName());
|
2018-07-13 12:33:46 +02:00
|
|
|
}));
|
|
|
|
|
}
|
2020-07-14 14:31:20 +02:00
|
|
|
InitializeRequest initRequest(params);
|
2020-02-20 10:11:35 +01:00
|
|
|
initRequest.setResponseCallback([this](const InitializeRequest::Response &initResponse){
|
2019-09-18 14:26:08 +02:00
|
|
|
initializeCallback(initResponse);
|
2018-07-13 12:33:46 +02:00
|
|
|
});
|
2020-10-29 12:38:05 +01:00
|
|
|
if (Utils::optional<ResponseHandler> responseHandler = initRequest.responseHandler())
|
|
|
|
|
m_responseHandlers[responseHandler->id] = responseHandler->callback;
|
|
|
|
|
|
2022-02-14 14:43:53 +01:00
|
|
|
// directly send message otherwise the state check of sendContent would fail
|
|
|
|
|
sendMessage(initRequest.toBaseMessage());
|
2018-07-13 12:33:46 +02:00
|
|
|
m_state = InitializeRequested;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::shutdown()
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_state == Initialized, emit finished(); return);
|
|
|
|
|
qCDebug(LOGLSPCLIENT) << "shutdown language server " << m_displayName;
|
|
|
|
|
ShutdownRequest shutdown;
|
2018-11-20 07:45:22 +01:00
|
|
|
shutdown.setResponseCallback([this](const ShutdownRequest::Response &shutdownResponse){
|
2018-07-13 12:33:46 +02:00
|
|
|
shutDownCallback(shutdownResponse);
|
|
|
|
|
});
|
|
|
|
|
sendContent(shutdown);
|
|
|
|
|
m_state = ShutdownRequested;
|
2022-02-22 07:42:54 +01:00
|
|
|
m_shutdownTimer.start();
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
Client::State Client::state() const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
return m_state;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-01 07:20:40 +01:00
|
|
|
QString Client::stateString() const
|
|
|
|
|
{
|
|
|
|
|
switch (m_state){
|
|
|
|
|
case Uninitialized: return tr("uninitialized");
|
|
|
|
|
case InitializeRequested: return tr("initialize requested");
|
|
|
|
|
case Initialized: return tr("initialized");
|
|
|
|
|
case ShutdownRequested: return tr("shutdown requested");
|
|
|
|
|
case Shutdown: return tr("shutdown");
|
|
|
|
|
case Error: return tr("error");
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-23 13:51:41 +01:00
|
|
|
ClientCapabilities Client::defaultClientCapabilities()
|
|
|
|
|
{
|
|
|
|
|
return generateClientCapabilities();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::setClientCapabilities(const LanguageServerProtocol::ClientCapabilities &caps)
|
|
|
|
|
{
|
|
|
|
|
m_clientCapabilities = caps;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 08:03:58 +02:00
|
|
|
void Client::openDocument(TextEditor::TextDocument *document)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
using namespace TextEditor;
|
2022-02-18 12:38:31 +01:00
|
|
|
if (m_openedDocument.contains(document) || !isSupportedDocument(document))
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
|
|
|
|
|
2021-08-11 12:37:52 +02:00
|
|
|
if (m_state != Initialized) {
|
|
|
|
|
m_postponedDocuments << document;
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
2021-08-11 12:37:52 +02:00
|
|
|
}
|
|
|
|
|
|
2019-05-28 13:49:26 +02:00
|
|
|
const FilePath &filePath = document->filePath();
|
2018-07-13 12:33:46 +02:00
|
|
|
const QString method(DidOpenTextDocumentNotification::methodName);
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
|
|
|
|
if (!registered.value())
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
2018-07-13 12:33:46 +02:00
|
|
|
const TextDocumentRegistrationOptions option(
|
2019-09-11 11:15:39 +02:00
|
|
|
m_dynamicCapabilities.option(method).toObject());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (option.isValid()
|
2019-09-11 11:15:39 +02:00
|
|
|
&& !option.filterApplies(filePath, Utils::mimeTypeForName(document->mimeType()))) {
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
} else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
|
|
|
|
|
= m_serverCapabilities.textDocumentSync()) {
|
|
|
|
|
if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) {
|
|
|
|
|
if (!options->openClose().value_or(true))
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-08-11 12:37:52 +02:00
|
|
|
|
|
|
|
|
m_openedDocument[document] = document->plainText();
|
2019-09-10 08:03:58 +02:00
|
|
|
connect(document, &TextDocument::contentsChangedWithPosition, this,
|
2019-09-11 11:15:39 +02:00
|
|
|
[this, document](int position, int charsRemoved, int charsAdded) {
|
2019-09-10 08:03:58 +02:00
|
|
|
documentContentsChanged(document, position, charsRemoved, charsAdded);
|
2019-09-11 11:15:39 +02:00
|
|
|
});
|
2018-07-13 12:33:46 +02:00
|
|
|
TextDocumentItem item;
|
2018-09-05 13:38:08 +02:00
|
|
|
item.setLanguageId(TextDocumentItem::mimeTypeToLanguageId(document->mimeType()));
|
2019-09-10 08:03:58 +02:00
|
|
|
item.setUri(DocumentUri::fromFilePath(filePath));
|
|
|
|
|
item.setText(document->plainText());
|
2021-06-14 11:47:04 +02:00
|
|
|
if (!m_documentVersions.contains(filePath))
|
|
|
|
|
m_documentVersions[filePath] = 0;
|
|
|
|
|
item.setVersion(m_documentVersions[filePath]);
|
2018-07-13 12:33:46 +02:00
|
|
|
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
|
2021-09-27 16:24:37 +02:00
|
|
|
handleDocumentOpened(document);
|
2019-03-26 13:48:06 +01:00
|
|
|
|
2020-05-12 09:20:01 +02:00
|
|
|
const Client *currentClient = LanguageClientManager::clientForDocument(document);
|
2021-04-20 11:40:24 +02:00
|
|
|
if (currentClient == this) {
|
|
|
|
|
// this is the active client for the document so directly activate it
|
2019-09-10 08:03:58 +02:00
|
|
|
activateDocument(document);
|
2021-04-20 11:40:24 +02:00
|
|
|
} else if (m_activateDocAutomatically && currentClient == nullptr) {
|
|
|
|
|
// there is no client for this document so assign it to this server
|
2020-05-12 09:20:01 +02:00
|
|
|
LanguageClientManager::openDocumentWithClient(document, this);
|
2021-04-20 11:40:24 +02:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-07-08 12:11:15 +02:00
|
|
|
void Client::sendContent(const IContent &content, SendDocUpdates sendUpdates)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-01-31 08:46:23 +01:00
|
|
|
QTC_ASSERT(m_clientInterface, return);
|
2018-07-13 12:33:46 +02:00
|
|
|
QTC_ASSERT(m_state == Initialized, return);
|
2021-07-08 12:11:15 +02:00
|
|
|
if (sendUpdates == SendDocUpdates::Send)
|
2021-09-14 17:00:16 +02:00
|
|
|
sendPostponedDocumentUpdates(Schedule::Delayed);
|
2020-10-29 12:38:05 +01:00
|
|
|
if (Utils::optional<ResponseHandler> responseHandler = content.responseHandler())
|
|
|
|
|
m_responseHandlers[responseHandler->id] = responseHandler->callback;
|
2018-07-13 12:33:46 +02:00
|
|
|
QString error;
|
|
|
|
|
if (!QTC_GUARD(content.isValid(&error)))
|
2020-12-17 10:24:33 +01:00
|
|
|
Core::MessageManager::writeFlashing(error);
|
2022-02-14 14:43:53 +01:00
|
|
|
sendMessage(content.toBaseMessage());
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::cancelRequest(const MessageId &id)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
m_responseHandlers.remove(id);
|
2021-07-08 12:11:15 +02:00
|
|
|
sendContent(CancelRequest(CancelParameter(id)), SendDocUpdates::Ignore);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-11 11:15:39 +02:00
|
|
|
void Client::closeDocument(TextEditor::TextDocument *document)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2020-01-08 12:03:54 +01:00
|
|
|
deactivateDocument(document);
|
2019-09-11 14:34:20 +02:00
|
|
|
const DocumentUri &uri = DocumentUri::fromFilePath(document->filePath());
|
2021-08-11 12:37:52 +02:00
|
|
|
m_postponedDocuments.remove(document);
|
2021-06-09 09:47:26 +02:00
|
|
|
if (m_openedDocument.remove(document) != 0) {
|
|
|
|
|
handleDocumentClosed(document);
|
|
|
|
|
if (m_state == Initialized) {
|
|
|
|
|
DidCloseTextDocumentParams params(TextDocumentIdentifier{uri});
|
|
|
|
|
sendContent(DidCloseTextDocumentNotification(params));
|
|
|
|
|
}
|
2020-01-08 12:03:54 +01:00
|
|
|
}
|
2019-09-10 08:03:58 +02:00
|
|
|
}
|
|
|
|
|
|
2021-01-20 12:46:07 +01:00
|
|
|
void Client::updateCompletionProvider(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
bool useLanguageServer = m_serverCapabilities.completionProvider().has_value();
|
|
|
|
|
auto clientCompletionProvider = static_cast<LanguageClientCompletionAssistProvider *>(
|
|
|
|
|
m_clientProviders.completionAssistProvider.data());
|
|
|
|
|
if (m_dynamicCapabilities.isRegistered(CompletionRequest::methodName).value_or(false)) {
|
|
|
|
|
const QJsonValue &options = m_dynamicCapabilities.option(CompletionRequest::methodName);
|
|
|
|
|
const TextDocumentRegistrationOptions docOptions(options);
|
|
|
|
|
useLanguageServer = docOptions.filterApplies(document->filePath(),
|
|
|
|
|
Utils::mimeTypeForName(document->mimeType()));
|
|
|
|
|
|
|
|
|
|
const ServerCapabilities::CompletionOptions completionOptions(options);
|
2021-02-26 08:29:15 +01:00
|
|
|
if (completionOptions.isValid())
|
2021-01-20 12:46:07 +01:00
|
|
|
clientCompletionProvider->setTriggerCharacters(completionOptions.triggerCharacters());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (document->completionAssistProvider() != clientCompletionProvider) {
|
|
|
|
|
if (useLanguageServer) {
|
|
|
|
|
m_resetAssistProvider[document].completionAssistProvider
|
|
|
|
|
= document->completionAssistProvider();
|
|
|
|
|
document->setCompletionAssistProvider(clientCompletionProvider);
|
|
|
|
|
}
|
|
|
|
|
} else if (!useLanguageServer) {
|
|
|
|
|
document->setCompletionAssistProvider(
|
|
|
|
|
m_resetAssistProvider[document].completionAssistProvider);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 09:53:50 +01:00
|
|
|
void Client::updateFunctionHintProvider(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
bool useLanguageServer = m_serverCapabilities.signatureHelpProvider().has_value();
|
|
|
|
|
auto clientFunctionHintProvider = static_cast<FunctionHintAssistProvider *>(
|
|
|
|
|
m_clientProviders.functionHintProvider.data());
|
|
|
|
|
if (m_dynamicCapabilities.isRegistered(SignatureHelpRequest::methodName).value_or(false)) {
|
|
|
|
|
const QJsonValue &options = m_dynamicCapabilities.option(SignatureHelpRequest::methodName);
|
|
|
|
|
const TextDocumentRegistrationOptions docOptions(options);
|
|
|
|
|
useLanguageServer = docOptions.filterApplies(document->filePath(),
|
|
|
|
|
Utils::mimeTypeForName(document->mimeType()));
|
|
|
|
|
|
|
|
|
|
const ServerCapabilities::SignatureHelpOptions signatureOptions(options);
|
2021-02-26 08:29:15 +01:00
|
|
|
if (signatureOptions.isValid())
|
2021-01-22 09:53:50 +01:00
|
|
|
clientFunctionHintProvider->setTriggerCharacters(signatureOptions.triggerCharacters());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (document->functionHintAssistProvider() != clientFunctionHintProvider) {
|
|
|
|
|
if (useLanguageServer) {
|
|
|
|
|
m_resetAssistProvider[document].functionHintProvider
|
|
|
|
|
= document->functionHintAssistProvider();
|
|
|
|
|
document->setFunctionHintAssistProvider(clientFunctionHintProvider);
|
|
|
|
|
}
|
|
|
|
|
} else if (!useLanguageServer) {
|
|
|
|
|
document->setFunctionHintAssistProvider(
|
|
|
|
|
m_resetAssistProvider[document].functionHintProvider);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-12 13:49:13 +01:00
|
|
|
void Client::requestDocumentHighlights(TextEditor::TextEditorWidget *widget)
|
2022-01-06 11:31:17 +01:00
|
|
|
{
|
|
|
|
|
QTimer *timer = m_documentHighlightsTimer[widget];
|
|
|
|
|
if (!timer) {
|
|
|
|
|
const auto uri = DocumentUri::fromFilePath(widget->textDocument()->filePath());
|
|
|
|
|
if (m_highlightRequests.contains(widget))
|
|
|
|
|
cancelRequest(m_highlightRequests.take(widget));
|
|
|
|
|
timer = new QTimer;
|
|
|
|
|
timer->setSingleShot(true);
|
|
|
|
|
m_documentHighlightsTimer.insert(widget, timer);
|
|
|
|
|
auto connection = connect(widget, &QWidget::destroyed, this, [widget, this]() {
|
|
|
|
|
delete m_documentHighlightsTimer.take(widget);
|
|
|
|
|
});
|
|
|
|
|
connect(timer, &QTimer::timeout, this, [this, widget, connection]() {
|
|
|
|
|
disconnect(connection);
|
|
|
|
|
requestDocumentHighlightsNow(widget);
|
|
|
|
|
m_documentHighlightsTimer.take(widget)->deleteLater();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
timer->start(250);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::requestDocumentHighlightsNow(TextEditor::TextEditorWidget *widget)
|
2021-03-12 13:49:13 +01:00
|
|
|
{
|
|
|
|
|
const auto uri = DocumentUri::fromFilePath(widget->textDocument()->filePath());
|
|
|
|
|
if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) {
|
|
|
|
|
TextDocumentRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(DocumentHighlightsRequest::methodName));
|
|
|
|
|
if (!option.filterApplies(widget->textDocument()->filePath()))
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
Utils::optional<Utils::variant<bool, WorkDoneProgressOptions>> provider
|
|
|
|
|
= m_serverCapabilities.documentHighlightProvider();
|
|
|
|
|
if (!provider.has_value())
|
|
|
|
|
return;
|
|
|
|
|
if (Utils::holds_alternative<bool>(*provider) && !Utils::get<bool>(*provider))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-30 11:14:04 +02:00
|
|
|
if (m_highlightRequests.contains(widget))
|
|
|
|
|
cancelRequest(m_highlightRequests.take(widget));
|
2021-03-12 13:49:13 +01:00
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
const QTextCursor adjustedCursor = adjustedCursorForHighlighting(widget->textCursor(),
|
|
|
|
|
widget->textDocument());
|
2021-03-12 13:49:13 +01:00
|
|
|
DocumentHighlightsRequest request(
|
2021-12-02 13:18:02 +01:00
|
|
|
TextDocumentPositionParams(TextDocumentIdentifier(uri), Position{adjustedCursor}));
|
2021-03-30 10:27:02 +02:00
|
|
|
auto connection = connect(widget, &QObject::destroyed, this, [this, widget]() {
|
2021-03-30 11:14:04 +02:00
|
|
|
if (m_highlightRequests.contains(widget))
|
|
|
|
|
cancelRequest(m_highlightRequests.take(widget));
|
2021-03-30 10:27:02 +02:00
|
|
|
});
|
2021-03-12 13:49:13 +01:00
|
|
|
request.setResponseCallback(
|
2021-03-30 10:27:02 +02:00
|
|
|
[widget, this, uri, connection]
|
2021-03-12 13:49:13 +01:00
|
|
|
(const DocumentHighlightsRequest::Response &response)
|
|
|
|
|
{
|
2021-03-30 10:27:02 +02:00
|
|
|
m_highlightRequests.remove(widget);
|
|
|
|
|
disconnect(connection);
|
2021-03-12 13:49:13 +01:00
|
|
|
const Id &id = TextEditor::TextEditorWidget::CodeSemanticsSelection;
|
|
|
|
|
QList<QTextEdit::ExtraSelection> selections;
|
|
|
|
|
const Utils::optional<DocumentHighlightsResult> &result = response.result();
|
|
|
|
|
if (!result.has_value() || holds_alternative<std::nullptr_t>(result.value())) {
|
|
|
|
|
widget->setExtraSelections(id, selections);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QTextCharFormat &format =
|
|
|
|
|
widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
|
|
|
|
|
QTextDocument *document = widget->document();
|
|
|
|
|
for (const auto &highlight : get<QList<DocumentHighlight>>(result.value())) {
|
|
|
|
|
QTextEdit::ExtraSelection selection{widget->textCursor(), format};
|
|
|
|
|
const int &start = highlight.range().start().toPositionInDocument(document);
|
|
|
|
|
const int &end = highlight.range().end().toPositionInDocument(document);
|
|
|
|
|
if (start < 0 || end < 0)
|
|
|
|
|
continue;
|
|
|
|
|
selection.cursor.setPosition(start);
|
|
|
|
|
selection.cursor.setPosition(end, QTextCursor::KeepAnchor);
|
|
|
|
|
selections << selection;
|
|
|
|
|
}
|
|
|
|
|
widget->setExtraSelections(id, selections);
|
|
|
|
|
});
|
2021-03-30 10:27:02 +02:00
|
|
|
m_highlightRequests[widget] = request.id();
|
2021-03-12 13:49:13 +01:00
|
|
|
sendContent(request);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 08:03:58 +02:00
|
|
|
void Client::activateDocument(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
2021-06-14 11:47:04 +02:00
|
|
|
const FilePath &filePath = document->filePath();
|
|
|
|
|
auto uri = DocumentUri::fromFilePath(filePath);
|
|
|
|
|
m_diagnosticManager.showDiagnostics(uri, m_documentVersions.value(filePath));
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.updateSemanticTokens(document);
|
2020-05-09 02:55:42 +02:00
|
|
|
// only replace the assist provider if the language server support it
|
2021-01-20 12:46:07 +01:00
|
|
|
updateCompletionProvider(document);
|
2021-01-22 09:53:50 +01:00
|
|
|
updateFunctionHintProvider(document);
|
2020-05-09 02:55:42 +02:00
|
|
|
if (m_serverCapabilities.codeActionProvider()) {
|
|
|
|
|
m_resetAssistProvider[document].quickFixAssistProvider = document->quickFixAssistProvider();
|
2019-09-10 08:03:58 +02:00
|
|
|
document->setQuickFixAssistProvider(m_clientProviders.quickFixAssistProvider);
|
|
|
|
|
}
|
2020-01-08 12:03:54 +01:00
|
|
|
document->setFormatter(new LanguageClientFormatter(document, this));
|
2019-09-10 08:03:58 +02:00
|
|
|
for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) {
|
|
|
|
|
updateEditorToolBar(editor);
|
2020-05-11 08:41:57 +02:00
|
|
|
if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) {
|
2021-03-12 13:49:13 +01:00
|
|
|
TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
|
|
|
|
|
widget->addHoverHandler(&m_hoverHandler);
|
|
|
|
|
requestDocumentHighlights(widget);
|
|
|
|
|
if (symbolSupport().supportsRename(document))
|
|
|
|
|
widget->addOptionalActions(TextEditor::TextEditorActionHandler::RenameSymbol);
|
2020-05-11 08:41:57 +02:00
|
|
|
}
|
2019-09-10 08:03:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::deactivateDocument(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
2020-09-09 08:15:11 +02:00
|
|
|
m_diagnosticManager.hideDiagnostics(document);
|
2019-09-11 11:15:39 +02:00
|
|
|
resetAssistProviders(document);
|
2020-01-08 12:03:54 +01:00
|
|
|
document->setFormatter(nullptr);
|
2021-11-29 09:44:46 +01:00
|
|
|
m_tokenSupport.clearHighlight(document);
|
2019-11-06 10:14:13 +01:00
|
|
|
for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) {
|
2021-03-12 13:49:13 +01:00
|
|
|
if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor)) {
|
|
|
|
|
TextEditor::TextEditorWidget *widget = textEditor->editorWidget();
|
|
|
|
|
widget->removeHoverHandler(&m_hoverHandler);
|
|
|
|
|
widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, {});
|
|
|
|
|
}
|
2019-11-06 10:14:13 +01:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-11 14:50:57 +01:00
|
|
|
void Client::documentClosed(Core::IDocument *document)
|
|
|
|
|
{
|
|
|
|
|
if (auto textDocument = qobject_cast<TextEditor::TextDocument *>(document))
|
|
|
|
|
closeDocument(textDocument);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-08 12:03:54 +01:00
|
|
|
bool Client::documentOpen(const TextEditor::TextDocument *document) const
|
2019-03-26 13:48:06 +01:00
|
|
|
{
|
2020-01-08 12:03:54 +01:00
|
|
|
return m_openedDocument.contains(const_cast<TextEditor::TextDocument *>(document));
|
2019-03-26 13:48:06 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 15:39:37 +02:00
|
|
|
TextEditor::TextDocument *Client::documentForFilePath(const Utils::FilePath &file) const
|
|
|
|
|
{
|
|
|
|
|
for (auto it = m_openedDocument.cbegin(); it != m_openedDocument.cend(); ++it) {
|
|
|
|
|
if (it.key()->filePath() == file)
|
|
|
|
|
return it.key();
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 11:15:39 +02:00
|
|
|
void Client::documentContentsSaved(TextEditor::TextDocument *document)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
if (!m_openedDocument.contains(document))
|
2018-07-13 12:33:46 +02:00
|
|
|
return;
|
|
|
|
|
bool sendMessage = true;
|
|
|
|
|
bool includeText = false;
|
|
|
|
|
const QString method(DidSaveTextDocumentNotification::methodName);
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
|
|
|
|
sendMessage = registered.value();
|
|
|
|
|
if (sendMessage) {
|
|
|
|
|
const TextDocumentSaveRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(method).toObject());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (option.isValid()) {
|
2018-07-13 12:33:46 +02:00
|
|
|
sendMessage = option.filterApplies(document->filePath(),
|
|
|
|
|
Utils::mimeTypeForName(document->mimeType()));
|
|
|
|
|
includeText = option.includeText().value_or(includeText);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
|
|
|
|
|
= m_serverCapabilities.textDocumentSync()) {
|
|
|
|
|
if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value())) {
|
|
|
|
|
if (Utils::optional<SaveOptions> saveOptions = options->save())
|
|
|
|
|
includeText = saveOptions.value().includeText().value_or(includeText);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!sendMessage)
|
|
|
|
|
return;
|
|
|
|
|
DidSaveTextDocumentParams params(
|
2019-09-11 14:34:20 +02:00
|
|
|
TextDocumentIdentifier(DocumentUri::fromFilePath(document->filePath())));
|
2018-07-13 12:33:46 +02:00
|
|
|
if (includeText)
|
2019-09-11 11:15:39 +02:00
|
|
|
params.setText(document->plainText());
|
2018-07-13 12:33:46 +02:00
|
|
|
sendContent(DidSaveTextDocumentNotification(params));
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::documentWillSave(Core::IDocument *document)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-05-28 13:49:26 +02:00
|
|
|
const FilePath &filePath = document->filePath();
|
2019-09-11 14:31:20 +02:00
|
|
|
auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
|
|
|
|
|
if (!m_openedDocument.contains(textDocument))
|
2018-07-13 12:33:46 +02:00
|
|
|
return;
|
2021-08-17 11:55:04 +02:00
|
|
|
bool sendMessage = false;
|
2018-07-13 12:33:46 +02:00
|
|
|
const QString method(WillSaveTextDocumentNotification::methodName);
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
|
|
|
|
sendMessage = registered.value();
|
|
|
|
|
if (sendMessage) {
|
|
|
|
|
const TextDocumentRegistrationOptions option(m_dynamicCapabilities.option(method));
|
2021-02-26 08:29:15 +01:00
|
|
|
if (option.isValid()) {
|
2018-07-13 12:33:46 +02:00
|
|
|
sendMessage = option.filterApplies(filePath,
|
|
|
|
|
Utils::mimeTypeForName(document->mimeType()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (Utils::optional<ServerCapabilities::TextDocumentSync> _sync
|
|
|
|
|
= m_serverCapabilities.textDocumentSync()) {
|
|
|
|
|
if (auto options = Utils::get_if<TextDocumentSyncOptions>(&_sync.value()))
|
|
|
|
|
sendMessage = options->willSave().value_or(sendMessage);
|
|
|
|
|
}
|
|
|
|
|
if (!sendMessage)
|
|
|
|
|
return;
|
|
|
|
|
const WillSaveTextDocumentParams params(
|
2019-09-11 14:34:20 +02:00
|
|
|
TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)));
|
2018-07-13 12:33:46 +02:00
|
|
|
sendContent(WillSaveTextDocumentNotification(params));
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-02 10:49:23 +02:00
|
|
|
void Client::documentContentsChanged(TextEditor::TextDocument *document,
|
|
|
|
|
int position,
|
|
|
|
|
int charsRemoved,
|
|
|
|
|
int charsAdded)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2020-02-25 13:27:46 +01:00
|
|
|
if (!m_openedDocument.contains(document) || !reachable())
|
2018-07-13 12:33:46 +02:00
|
|
|
return;
|
|
|
|
|
const QString method(DidChangeTextDocumentNotification::methodName);
|
|
|
|
|
TextDocumentSyncKind syncKind = m_serverCapabilities.textDocumentSyncKindHelper();
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
2021-12-03 13:09:46 +02:00
|
|
|
syncKind = registered.value() ? TextDocumentSyncKind::Full : TextDocumentSyncKind::None;
|
2018-07-13 12:33:46 +02:00
|
|
|
if (syncKind != TextDocumentSyncKind::None) {
|
|
|
|
|
const TextDocumentChangeRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(method).toObject());
|
2021-02-26 08:29:15 +01:00
|
|
|
syncKind = option.isValid() ? option.syncKind() : syncKind;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 09:48:44 +01:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
if (syncKind != TextDocumentSyncKind::None) {
|
2019-04-02 10:49:23 +02:00
|
|
|
if (syncKind == TextDocumentSyncKind::Incremental) {
|
2021-09-13 17:40:41 +02:00
|
|
|
// If the new change is a pure insertion and its range is adjacent to the range of the
|
|
|
|
|
// previous change, we can trivially merge the two changes.
|
|
|
|
|
// For the typical case of the user typing a continuous sequence of characters,
|
|
|
|
|
// this will save a lot of TextDocumentContentChangeEvent elements in the data stream,
|
|
|
|
|
// as otherwise we'd send tons of single-character changes.
|
|
|
|
|
const QString &text = document->textAt(position, charsAdded);
|
|
|
|
|
auto &queue = m_documentsToUpdate[document];
|
|
|
|
|
bool append = true;
|
|
|
|
|
if (!queue.isEmpty() && charsRemoved == 0) {
|
|
|
|
|
auto &prev = queue.last();
|
|
|
|
|
const int prevStart = prev.range()->start()
|
|
|
|
|
.toPositionInDocument(document->document());
|
|
|
|
|
if (prevStart + prev.text().length() == position) {
|
|
|
|
|
prev.setText(prev.text() + text);
|
|
|
|
|
append = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (append) {
|
|
|
|
|
QTextDocument oldDoc(m_openedDocument[document]);
|
|
|
|
|
QTextCursor cursor(&oldDoc);
|
|
|
|
|
// Workaround https://bugreports.qt.io/browse/QTBUG-80662
|
|
|
|
|
// The contentsChanged gives a character count that can be wrong for QTextCursor
|
|
|
|
|
// when there are special characters removed/added (like formating characters).
|
|
|
|
|
// Also, characterCount return the number of characters + 1 because of the hidden
|
|
|
|
|
// paragraph separator character.
|
|
|
|
|
// This implementation is based on QWidgetTextControlPrivate::_q_contentsChanged.
|
|
|
|
|
// For charsAdded, textAt handles the case itself.
|
|
|
|
|
cursor.setPosition(qMin(oldDoc.characterCount() - 1, position + charsRemoved));
|
|
|
|
|
cursor.setPosition(position, QTextCursor::KeepAnchor);
|
|
|
|
|
DidChangeTextDocumentParams::TextDocumentContentChangeEvent change;
|
|
|
|
|
change.setRange(Range(cursor));
|
|
|
|
|
change.setRangeLength(cursor.selectionEnd() - cursor.selectionStart());
|
|
|
|
|
change.setText(text);
|
|
|
|
|
queue << change;
|
|
|
|
|
}
|
2019-04-02 10:49:23 +02:00
|
|
|
} else {
|
2020-07-14 14:31:20 +02:00
|
|
|
m_documentsToUpdate[document] = {
|
|
|
|
|
DidChangeTextDocumentParams::TextDocumentContentChangeEvent(document->plainText())};
|
2019-04-02 10:49:23 +02:00
|
|
|
}
|
2019-09-11 14:31:20 +02:00
|
|
|
m_openedDocument[document] = document->plainText();
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
2019-01-25 09:48:44 +01:00
|
|
|
|
2021-09-07 11:47:21 +02:00
|
|
|
++m_documentVersions[document->filePath()];
|
2020-06-17 14:15:13 +02:00
|
|
|
using namespace TextEditor;
|
|
|
|
|
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(document)) {
|
2021-08-31 06:31:13 +02:00
|
|
|
TextEditorWidget *widget = editor->editorWidget();
|
|
|
|
|
QTC_ASSERT(widget, continue);
|
|
|
|
|
delete m_documentHighlightsTimer.take(widget);
|
|
|
|
|
widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id()));
|
2019-01-25 09:48:44 +01:00
|
|
|
}
|
2020-06-17 14:15:13 +02:00
|
|
|
m_documentUpdateTimer.start();
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::registerCapabilities(const QList<Registration> ®istrations)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
m_dynamicCapabilities.registerCapability(registrations);
|
2021-04-08 11:44:12 +02:00
|
|
|
for (const Registration ®istration : registrations) {
|
|
|
|
|
if (registration.method() == CompletionRequest::methodName) {
|
|
|
|
|
for (auto document : m_openedDocument.keys())
|
|
|
|
|
updateCompletionProvider(document);
|
|
|
|
|
}
|
|
|
|
|
if (registration.method() == SignatureHelpRequest::methodName) {
|
|
|
|
|
for (auto document : m_openedDocument.keys())
|
|
|
|
|
updateFunctionHintProvider(document);
|
|
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
if (registration.method() == "textDocument/semanticTokens") {
|
|
|
|
|
SemanticTokensOptions options(registration.registerOptions());
|
|
|
|
|
if (options.isValid())
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.setLegend(options.legend());
|
2021-02-25 12:54:14 +01:00
|
|
|
for (auto document : m_openedDocument.keys())
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.updateSemanticTokens(document);
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
2021-01-22 09:53:50 +01:00
|
|
|
}
|
2021-02-11 09:32:11 +01:00
|
|
|
emit capabilitiesChanged(m_dynamicCapabilities);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::unregisterCapabilities(const QList<Unregistration> &unregistrations)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
m_dynamicCapabilities.unregisterCapability(unregistrations);
|
2021-04-08 11:44:12 +02:00
|
|
|
for (const Unregistration &unregistration : unregistrations) {
|
|
|
|
|
if (unregistration.method() == CompletionRequest::methodName) {
|
|
|
|
|
for (auto document : m_openedDocument.keys())
|
|
|
|
|
updateCompletionProvider(document);
|
|
|
|
|
}
|
|
|
|
|
if (unregistration.method() == SignatureHelpRequest::methodName) {
|
|
|
|
|
for (auto document : m_openedDocument.keys())
|
|
|
|
|
updateFunctionHintProvider(document);
|
|
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
if (unregistration.method() == "textDocument/semanticTokens") {
|
|
|
|
|
for (auto document : m_openedDocument.keys())
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.updateSemanticTokens(document);
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
2021-04-08 11:44:12 +02:00
|
|
|
}
|
2021-02-11 09:32:11 +01:00
|
|
|
emit capabilitiesChanged(m_dynamicCapabilities);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info)
|
|
|
|
|
{
|
2021-02-26 08:29:15 +01:00
|
|
|
if (!info.isValid())
|
2018-07-13 12:33:46 +02:00
|
|
|
return {};
|
|
|
|
|
const Position &start = info.location().range().start();
|
2019-09-11 13:49:18 +02:00
|
|
|
return TextEditor::HighlightingResult(start.line() + 1,
|
|
|
|
|
start.character() + 1,
|
|
|
|
|
info.name().length(),
|
2019-06-12 12:55:06 +02:00
|
|
|
info.kind());
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::cursorPositionChanged(TextEditor::TextEditorWidget *widget)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-03-12 13:49:13 +01:00
|
|
|
TextEditor::TextDocument *document = widget->textDocument();
|
2021-09-14 12:13:56 +02:00
|
|
|
if (m_documentsToUpdate.find(document) != m_documentsToUpdate.end())
|
2020-06-17 14:15:13 +02:00
|
|
|
return; // we are currently changing this document so postpone the DocumentHighlightsRequest
|
2022-01-06 11:31:17 +01:00
|
|
|
requestDocumentHighlights(widget);
|
2021-03-12 13:49:13 +01:00
|
|
|
const Id selectionsId(TextEditor::TextEditorWidget::CodeSemanticsSelection);
|
|
|
|
|
const QList semanticSelections = widget->extraSelections(selectionsId);
|
|
|
|
|
if (!semanticSelections.isEmpty()) {
|
|
|
|
|
auto selectionContainsPos =
|
|
|
|
|
[pos = widget->position()](const QTextEdit::ExtraSelection &selection) {
|
|
|
|
|
const QTextCursor cursor = selection.cursor;
|
|
|
|
|
return cursor.selectionStart() <= pos && cursor.selectionEnd() >= pos;
|
|
|
|
|
};
|
|
|
|
|
if (!Utils::anyOf(semanticSelections, selectionContainsPos))
|
|
|
|
|
widget->setExtraSelections(selectionsId, {});
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-06 10:26:31 +02:00
|
|
|
SymbolSupport &Client::symbolSupport()
|
|
|
|
|
{
|
|
|
|
|
return m_symbolSupport;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::requestCodeActions(const DocumentUri &uri, const QList<Diagnostic> &diagnostics)
|
2019-01-25 09:48:44 +01:00
|
|
|
{
|
2019-09-11 14:34:20 +02:00
|
|
|
const Utils::FilePath fileName = uri.toFilePath();
|
2019-10-01 09:11:02 +02:00
|
|
|
TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFilePath(fileName);
|
2019-01-25 09:48:44 +01:00
|
|
|
if (!doc)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
CodeActionParams codeActionParams;
|
|
|
|
|
CodeActionParams::CodeActionContext context;
|
|
|
|
|
context.setDiagnostics(diagnostics);
|
|
|
|
|
codeActionParams.setContext(context);
|
2020-07-14 14:31:20 +02:00
|
|
|
codeActionParams.setTextDocument(TextDocumentIdentifier(uri));
|
2019-01-25 09:48:44 +01:00
|
|
|
Position start(0, 0);
|
|
|
|
|
const QTextBlock &lastBlock = doc->document()->lastBlock();
|
|
|
|
|
Position end(lastBlock.blockNumber(), lastBlock.length() - 1);
|
|
|
|
|
codeActionParams.setRange(Range(start, end));
|
|
|
|
|
CodeActionRequest request(codeActionParams);
|
|
|
|
|
request.setResponseCallback(
|
2019-01-31 12:15:43 +01:00
|
|
|
[uri, self = QPointer<Client>(this)](const CodeActionRequest::Response &response) {
|
2019-01-25 09:48:44 +01:00
|
|
|
if (self)
|
|
|
|
|
self->handleCodeActionResponse(response, uri);
|
|
|
|
|
});
|
2019-01-29 13:20:58 +01:00
|
|
|
requestCodeActions(request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::requestCodeActions(const CodeActionRequest &request)
|
|
|
|
|
{
|
|
|
|
|
if (!request.isValid(nullptr))
|
|
|
|
|
return;
|
|
|
|
|
|
2019-05-28 13:49:26 +02:00
|
|
|
const Utils::FilePath fileName
|
2019-09-11 14:34:20 +02:00
|
|
|
= request.params().value_or(CodeActionParams()).textDocument().uri().toFilePath();
|
2019-01-29 13:20:58 +01:00
|
|
|
|
|
|
|
|
const QString method(CodeActionRequest::methodName);
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
|
|
|
|
if (!registered.value())
|
|
|
|
|
return;
|
|
|
|
|
const TextDocumentRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(method).toObject());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (option.isValid() && !option.filterApplies(fileName))
|
2019-01-29 13:20:58 +01:00
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
Utils::variant<bool, CodeActionOptions> provider
|
|
|
|
|
= m_serverCapabilities.codeActionProvider().value_or(false);
|
|
|
|
|
if (!(Utils::holds_alternative<CodeActionOptions>(provider) || Utils::get<bool>(provider)))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 09:48:44 +01:00
|
|
|
sendContent(request);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::handleCodeActionResponse(const CodeActionRequest::Response &response,
|
2019-01-25 09:48:44 +01:00
|
|
|
const DocumentUri &uri)
|
|
|
|
|
{
|
|
|
|
|
if (const Utils::optional<CodeActionRequest::Response::Error> &error = response.error())
|
|
|
|
|
log(*error);
|
|
|
|
|
if (const Utils::optional<CodeActionResult> &_result = response.result()) {
|
|
|
|
|
const CodeActionResult &result = _result.value();
|
|
|
|
|
if (auto list = Utils::get_if<QList<Utils::variant<Command, CodeAction>>>(&result)) {
|
|
|
|
|
for (const Utils::variant<Command, CodeAction> &item : *list) {
|
|
|
|
|
if (auto action = Utils::get_if<CodeAction>(&item))
|
|
|
|
|
updateCodeActionRefactoringMarker(this, *action, uri);
|
2019-02-03 22:51:04 +02:00
|
|
|
else if (auto command = Utils::get_if<Command>(&item)) {
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(command) // todo
|
2019-02-03 22:51:04 +02:00
|
|
|
}
|
2019-01-25 09:48:44 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::executeCommand(const Command &command)
|
2019-01-25 09:48:44 +01:00
|
|
|
{
|
2021-01-22 09:51:32 +01:00
|
|
|
bool serverSupportsExecuteCommand = m_serverCapabilities.executeCommandProvider().has_value();
|
|
|
|
|
serverSupportsExecuteCommand = m_dynamicCapabilities
|
|
|
|
|
.isRegistered(ExecuteCommandRequest::methodName)
|
|
|
|
|
.value_or(serverSupportsExecuteCommand);
|
|
|
|
|
if (serverSupportsExecuteCommand)
|
|
|
|
|
sendContent(ExecuteCommandRequest(ExecuteCommandParams(command)));
|
2019-01-25 09:48:44 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-23 12:11:31 +02:00
|
|
|
ProjectExplorer::Project *Client::project() const
|
2019-03-15 11:25:48 +01:00
|
|
|
{
|
|
|
|
|
return m_project;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::setCurrentProject(ProjectExplorer::Project *project)
|
|
|
|
|
{
|
2021-11-10 12:26:12 +01:00
|
|
|
if (m_project == project)
|
|
|
|
|
return;
|
|
|
|
|
if (m_project)
|
|
|
|
|
m_project->disconnect(this);
|
2019-03-15 11:25:48 +01:00
|
|
|
m_project = project;
|
2021-11-10 12:26:12 +01:00
|
|
|
if (m_project) {
|
|
|
|
|
connect(m_project, &ProjectExplorer::Project::destroyed, this, [this]() {
|
|
|
|
|
// the project of the client should already be null since we expect the session and
|
|
|
|
|
// the language client manager to reset it before it gets deleted.
|
|
|
|
|
QTC_ASSERT(m_project == nullptr, projectClosed(m_project));
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-03-15 11:25:48 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::projectOpened(ProjectExplorer::Project *project)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
if (!sendWorkspceFolderChanges())
|
|
|
|
|
return;
|
|
|
|
|
WorkspaceFoldersChangeEvent event;
|
2020-08-12 13:07:14 +02:00
|
|
|
event.setAdded({WorkSpaceFolder(DocumentUri::fromFilePath(project->projectDirectory()),
|
|
|
|
|
project->displayName())});
|
2018-07-13 12:33:46 +02:00
|
|
|
DidChangeWorkspaceFoldersParams params;
|
|
|
|
|
params.setEvent(event);
|
|
|
|
|
DidChangeWorkspaceFoldersNotification change(params);
|
|
|
|
|
sendContent(change);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::projectClosed(ProjectExplorer::Project *project)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-02-04 11:31:26 +01:00
|
|
|
if (sendWorkspceFolderChanges()) {
|
|
|
|
|
WorkspaceFoldersChangeEvent event;
|
|
|
|
|
event.setRemoved({WorkSpaceFolder(DocumentUri::fromFilePath(project->projectDirectory()),
|
|
|
|
|
project->displayName())});
|
|
|
|
|
DidChangeWorkspaceFoldersParams params;
|
|
|
|
|
params.setEvent(event);
|
|
|
|
|
DidChangeWorkspaceFoldersNotification change(params);
|
|
|
|
|
sendContent(change);
|
|
|
|
|
}
|
2019-03-15 11:25:48 +01:00
|
|
|
if (project == m_project) {
|
|
|
|
|
if (m_state == Initialized) {
|
|
|
|
|
shutdown();
|
|
|
|
|
} else {
|
|
|
|
|
m_state = Shutdown; // otherwise the manager would try to restart this server
|
|
|
|
|
emit finished();
|
|
|
|
|
}
|
2021-02-23 13:38:30 +01:00
|
|
|
m_project = nullptr;
|
2019-03-15 11:25:48 +01:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::setSupportedLanguage(const LanguageFilter &filter)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2018-10-10 14:26:57 +02:00
|
|
|
m_languagFilter = filter;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-20 11:40:24 +02:00
|
|
|
void Client::setActivateDocumentAutomatically(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
m_activateDocAutomatically = enabled;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-09 02:51:19 +02:00
|
|
|
void Client::setInitializationOptions(const QJsonObject &initializationOptions)
|
|
|
|
|
{
|
|
|
|
|
m_initializationOptions = initializationOptions;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 11:15:39 +02:00
|
|
|
bool Client::isSupportedDocument(const TextEditor::TextDocument *document) const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2018-10-10 14:26:57 +02:00
|
|
|
QTC_ASSERT(document, return false);
|
2019-03-12 11:01:25 +01:00
|
|
|
return m_languagFilter.isSupported(document);
|
2018-11-28 08:16:19 +01:00
|
|
|
}
|
|
|
|
|
|
2019-05-28 13:49:26 +02:00
|
|
|
bool Client::isSupportedFile(const Utils::FilePath &filePath, const QString &mimeType) const
|
2018-11-28 08:16:19 +01:00
|
|
|
{
|
2019-03-11 15:13:36 +01:00
|
|
|
return m_languagFilter.isSupported(filePath, mimeType);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::isSupportedUri(const DocumentUri &uri) const
|
2018-11-28 08:16:19 +01:00
|
|
|
{
|
2021-05-18 14:35:07 +02:00
|
|
|
const FilePath &filePath = uri.toFilePath();
|
|
|
|
|
return m_languagFilter.isSupported(filePath, Utils::mimeTypeForFile(filePath).name());
|
2018-11-28 08:16:19 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-26 09:21:57 +01:00
|
|
|
void Client::addAssistProcessor(TextEditor::IAssistProcessor *processor)
|
|
|
|
|
{
|
|
|
|
|
m_runningAssistProcessors.insert(processor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::removeAssistProcessor(TextEditor::IAssistProcessor *processor)
|
|
|
|
|
{
|
|
|
|
|
m_runningAssistProcessors.remove(processor);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-01 14:16:34 +01:00
|
|
|
QList<Diagnostic> Client::diagnosticsAt(const DocumentUri &uri, const QTextCursor &cursor) const
|
2019-01-29 13:20:58 +01:00
|
|
|
{
|
2021-02-01 14:16:34 +01:00
|
|
|
return m_diagnosticManager.diagnosticsAt(uri, cursor);
|
2019-01-29 13:20:58 +01:00
|
|
|
}
|
|
|
|
|
|
2021-06-02 17:51:31 +02:00
|
|
|
bool Client::hasDiagnostic(const LanguageServerProtocol::DocumentUri &uri,
|
|
|
|
|
const LanguageServerProtocol::Diagnostic &diag) const
|
|
|
|
|
{
|
|
|
|
|
return m_diagnosticManager.hasDiagnostic(uri, documentForFilePath(uri.toFilePath()), diag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::setDiagnosticsHandlers(const TextMarkCreator &textMarkCreator,
|
|
|
|
|
const HideDiagnosticsHandler &hideHandler)
|
|
|
|
|
{
|
|
|
|
|
m_diagnosticManager.setDiagnosticsHandlers(textMarkCreator, hideHandler);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-09 09:47:26 +02:00
|
|
|
void Client::setSemanticTokensHandler(const SemanticTokensHandler &handler)
|
|
|
|
|
{
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.setTokensHandler(handler);
|
2021-06-09 09:47:26 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:28:25 +02:00
|
|
|
void Client::setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier)
|
|
|
|
|
{
|
|
|
|
|
m_symbolStringifier = stringifier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SymbolStringifier Client::symbolStringifier() const
|
|
|
|
|
{
|
|
|
|
|
return m_symbolStringifier;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-18 16:30:03 +02:00
|
|
|
void Client::setSnippetsGroup(const QString &group)
|
|
|
|
|
{
|
|
|
|
|
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
|
|
|
|
m_clientProviders.completionAssistProvider)) {
|
|
|
|
|
provider->setSnippetsGroup(group);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-13 16:09:04 +02:00
|
|
|
void Client::setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider)
|
|
|
|
|
{
|
|
|
|
|
delete m_clientProviders.completionAssistProvider;
|
|
|
|
|
m_clientProviders.completionAssistProvider = provider;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 14:01:24 +01:00
|
|
|
void Client::start()
|
2019-01-31 08:46:23 +01:00
|
|
|
{
|
2021-08-25 13:55:45 +02:00
|
|
|
LanguageClientManager::addClient(this);
|
2021-02-12 14:01:24 +01:00
|
|
|
if (m_clientInterface->start())
|
|
|
|
|
LanguageClientManager::clientStarted(this);
|
|
|
|
|
else
|
|
|
|
|
LanguageClientManager::clientFinished(this);
|
2019-01-31 08:46:23 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::reset()
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2018-09-14 10:00:29 +02:00
|
|
|
if (!m_restartsLeft)
|
|
|
|
|
return false;
|
|
|
|
|
--m_restartsLeft;
|
2018-07-13 12:33:46 +02:00
|
|
|
m_state = Uninitialized;
|
|
|
|
|
m_responseHandlers.clear();
|
2019-01-31 08:46:23 +01:00
|
|
|
m_clientInterface->resetBuffer();
|
2019-04-02 10:49:23 +02:00
|
|
|
updateEditorToolBar(m_openedDocument.keys());
|
2018-07-13 12:33:46 +02:00
|
|
|
m_serverCapabilities = ServerCapabilities();
|
|
|
|
|
m_dynamicCapabilities.reset();
|
2020-09-09 08:15:11 +02:00
|
|
|
m_diagnosticManager.clearDiagnostics();
|
2020-12-08 15:41:46 +01:00
|
|
|
for (auto it = m_openedDocument.cbegin(); it != m_openedDocument.cend(); ++it)
|
|
|
|
|
it.key()->disconnect(this);
|
2021-05-06 13:23:43 +02:00
|
|
|
m_openedDocument.clear();
|
2021-02-16 12:14:11 +01:00
|
|
|
// temporary container needed since m_resetAssistProvider is changed in resetAssistProviders
|
|
|
|
|
for (TextEditor::TextDocument *document : m_resetAssistProvider.keys())
|
|
|
|
|
resetAssistProviders(document);
|
2020-12-08 15:41:46 +01:00
|
|
|
for (TextEditor::IAssistProcessor *processor : qAsConst(m_runningAssistProcessors))
|
2020-03-26 09:21:57 +01:00
|
|
|
processor->setAsyncProposalAvailable(nullptr);
|
|
|
|
|
m_runningAssistProcessors.clear();
|
2021-03-12 13:49:13 +01:00
|
|
|
qDeleteAll(m_documentHighlightsTimer);
|
|
|
|
|
m_documentHighlightsTimer.clear();
|
2021-06-03 09:10:10 +02:00
|
|
|
m_progressManager.reset();
|
2021-06-14 11:47:04 +02:00
|
|
|
m_documentVersions.clear();
|
2018-09-14 10:00:29 +02:00
|
|
|
return true;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::setError(const QString &message)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
log(message);
|
|
|
|
|
m_state = Error;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-20 15:13:10 +02:00
|
|
|
void Client::setProgressTitleForToken(const LanguageServerProtocol::ProgressToken &token,
|
|
|
|
|
const QString &message)
|
|
|
|
|
{
|
|
|
|
|
m_progressManager.setTitleForToken(token, message);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::handleMessage(const BaseMessage &message)
|
2019-01-31 08:46:23 +01:00
|
|
|
{
|
2020-02-20 10:13:14 +01:00
|
|
|
LanguageClientManager::logBaseMessage(LspLogMessage::ServerMessage, name(), message);
|
2019-01-31 08:46:23 +01:00
|
|
|
if (auto handler = m_contentHandler[message.mimeType]) {
|
|
|
|
|
QString parseError;
|
|
|
|
|
handler(message.content, message.codec, parseError,
|
2020-10-19 10:15:20 +02:00
|
|
|
[this](const MessageId &id, const QByteArray &content, QTextCodec *codec){
|
2019-01-31 08:46:23 +01:00
|
|
|
this->handleResponse(id, content, codec);
|
|
|
|
|
},
|
2020-10-19 10:15:20 +02:00
|
|
|
[this](const QString &method, const MessageId &id, const IContent *content){
|
2019-01-31 08:46:23 +01:00
|
|
|
this->handleMethod(method, id, content);
|
|
|
|
|
});
|
|
|
|
|
if (!parseError.isEmpty())
|
|
|
|
|
log(parseError);
|
|
|
|
|
} else {
|
|
|
|
|
log(tr("Cannot handle content of type: %1").arg(QLatin1String(message.mimeType)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-15 12:01:53 +01:00
|
|
|
void Client::log(const QString &message) const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-10-15 10:40:02 +02:00
|
|
|
switch (m_logTarget) {
|
|
|
|
|
case LogTarget::Ui:
|
|
|
|
|
Core::MessageManager::writeFlashing(QString("LanguageClient %1: %2").arg(name(), message));
|
|
|
|
|
break;
|
|
|
|
|
case LogTarget::Console:
|
|
|
|
|
qCDebug(LOGLSPCLIENT) << message;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
const ServerCapabilities &Client::capabilities() const
|
2018-11-23 09:45:51 +01:00
|
|
|
{
|
|
|
|
|
return m_serverCapabilities;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
const DynamicCapabilities &Client::dynamicCapabilities() const
|
2018-11-23 09:45:51 +01:00
|
|
|
{
|
|
|
|
|
return m_dynamicCapabilities;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-04 14:36:28 +02:00
|
|
|
DocumentSymbolCache *Client::documentSymbolCache()
|
|
|
|
|
{
|
|
|
|
|
return &m_documentSymbolCache;
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-15 11:19:31 +02:00
|
|
|
HoverHandler *Client::hoverHandler()
|
|
|
|
|
{
|
|
|
|
|
return &m_hoverHandler;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 10:24:33 +01:00
|
|
|
void Client::log(const ShowMessageParams &message)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2020-12-17 10:24:33 +01:00
|
|
|
log(message.toString());
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-18 08:51:35 +01:00
|
|
|
LanguageClientValue<MessageActionItem> Client::showMessageBox(
|
|
|
|
|
const ShowMessageRequestParams &message)
|
2018-09-12 10:24:02 +02:00
|
|
|
{
|
|
|
|
|
auto box = new QMessageBox();
|
|
|
|
|
box->setText(message.toString());
|
|
|
|
|
box->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
|
switch (message.type()) {
|
|
|
|
|
case Error: box->setIcon(QMessageBox::Critical); break;
|
|
|
|
|
case Warning: box->setIcon(QMessageBox::Warning); break;
|
|
|
|
|
case Info: box->setIcon(QMessageBox::Information); break;
|
|
|
|
|
case Log: box->setIcon(QMessageBox::NoIcon); break;
|
|
|
|
|
}
|
|
|
|
|
QHash<QAbstractButton *, MessageActionItem> itemForButton;
|
|
|
|
|
if (const Utils::optional<QList<MessageActionItem>> actions = message.actions()) {
|
|
|
|
|
for (const MessageActionItem &action : actions.value())
|
|
|
|
|
itemForButton.insert(box->addButton(action.title(), QMessageBox::InvalidRole), action);
|
|
|
|
|
}
|
2021-11-18 08:51:35 +01:00
|
|
|
box->exec();
|
|
|
|
|
const MessageActionItem &item = itemForButton.value(box->clickedButton());
|
|
|
|
|
return item.isValid() ? LanguageClientValue<MessageActionItem>(item)
|
|
|
|
|
: LanguageClientValue<MessageActionItem>();
|
2018-09-12 10:24:02 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 08:03:36 +02:00
|
|
|
void Client::resetAssistProviders(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
const AssistProviders providers = m_resetAssistProvider.take(document);
|
2020-05-09 02:55:42 +02:00
|
|
|
|
2020-09-08 12:23:20 +02:00
|
|
|
if (document->completionAssistProvider() == m_clientProviders.completionAssistProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setCompletionAssistProvider(providers.completionAssistProvider);
|
2020-05-09 02:55:42 +02:00
|
|
|
|
2020-09-08 12:23:20 +02:00
|
|
|
if (document->functionHintAssistProvider() == m_clientProviders.functionHintProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setFunctionHintAssistProvider(providers.functionHintProvider);
|
2020-05-09 02:55:42 +02:00
|
|
|
|
2020-09-08 12:23:20 +02:00
|
|
|
if (document->quickFixAssistProvider() == m_clientProviders.quickFixAssistProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setQuickFixAssistProvider(providers.quickFixAssistProvider);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-14 17:00:16 +02:00
|
|
|
void Client::sendPostponedDocumentUpdates(Schedule semanticTokensSchedule)
|
2020-06-17 14:15:13 +02:00
|
|
|
{
|
|
|
|
|
m_documentUpdateTimer.stop();
|
2021-09-14 12:13:56 +02:00
|
|
|
if (m_documentsToUpdate.empty())
|
2020-06-17 14:15:13 +02:00
|
|
|
return;
|
|
|
|
|
TextEditor::TextEditorWidget *currentWidget
|
|
|
|
|
= TextEditor::TextEditorWidget::currentTextEditorWidget();
|
2020-09-10 09:12:20 +02:00
|
|
|
|
|
|
|
|
struct DocumentUpdate
|
|
|
|
|
{
|
|
|
|
|
TextEditor::TextDocument *document;
|
|
|
|
|
DidChangeTextDocumentNotification notification;
|
|
|
|
|
};
|
2021-09-14 12:13:56 +02:00
|
|
|
const auto updates = Utils::transform<QList<DocumentUpdate>>(m_documentsToUpdate,
|
|
|
|
|
[this](const auto &elem) {
|
|
|
|
|
TextEditor::TextDocument * const document = elem.first;
|
2021-06-14 11:47:04 +02:00
|
|
|
const FilePath &filePath = document->filePath();
|
|
|
|
|
const auto uri = DocumentUri::fromFilePath(filePath);
|
2020-06-17 14:15:13 +02:00
|
|
|
VersionedTextDocumentIdentifier docId(uri);
|
2021-09-07 11:47:21 +02:00
|
|
|
docId.setVersion(m_documentVersions[filePath]);
|
2020-06-17 14:15:13 +02:00
|
|
|
DidChangeTextDocumentParams params;
|
|
|
|
|
params.setTextDocument(docId);
|
2021-09-14 12:13:56 +02:00
|
|
|
params.setContentChanges(elem.second);
|
|
|
|
|
return DocumentUpdate{document, DidChangeTextDocumentNotification(params)};
|
|
|
|
|
});
|
|
|
|
|
m_documentsToUpdate.clear();
|
2020-09-10 09:12:20 +02:00
|
|
|
|
2021-09-14 12:13:56 +02:00
|
|
|
for (const DocumentUpdate &update : updates) {
|
|
|
|
|
sendContent(update.notification, SendDocUpdates::Ignore);
|
2020-09-10 09:12:20 +02:00
|
|
|
emit documentUpdated(update.document);
|
|
|
|
|
|
|
|
|
|
if (currentWidget && currentWidget->textDocument() == update.document)
|
2021-07-09 14:09:36 +02:00
|
|
|
requestDocumentHighlights(currentWidget);
|
2021-02-25 12:54:14 +01:00
|
|
|
|
2021-09-14 17:00:16 +02:00
|
|
|
switch (semanticTokensSchedule) {
|
|
|
|
|
case Schedule::Now:
|
2021-09-14 13:16:43 +02:00
|
|
|
m_tokenSupport.updateSemanticTokens(update.document);
|
2021-09-14 17:00:16 +02:00
|
|
|
break;
|
|
|
|
|
case Schedule::Delayed:
|
2021-09-14 13:16:43 +02:00
|
|
|
QTimer::singleShot(m_documentUpdateTimer.interval(), this,
|
|
|
|
|
[this, doc = QPointer(update.document)] {
|
|
|
|
|
if (doc && m_documentsToUpdate.find(doc) == m_documentsToUpdate.end())
|
|
|
|
|
m_tokenSupport.updateSemanticTokens(doc);
|
|
|
|
|
});
|
2021-09-14 17:00:16 +02:00
|
|
|
break;
|
2021-09-14 13:16:43 +02:00
|
|
|
}
|
2020-06-17 14:15:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::handleResponse(const MessageId &id, const QByteArray &content, QTextCodec *codec)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
if (auto handler = m_responseHandlers[id])
|
|
|
|
|
handler(content, codec);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 08:51:35 +01:00
|
|
|
template<typename T>
|
|
|
|
|
static ResponseError<T> createInvalidParamsError(const QString &message)
|
|
|
|
|
{
|
|
|
|
|
ResponseError<T> error;
|
|
|
|
|
error.setMessage(message);
|
|
|
|
|
error.setCode(ResponseError<T>::InvalidParams);
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-19 10:15:20 +02:00
|
|
|
void Client::handleMethod(const QString &method, const MessageId &id, const IContent *content)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-11-18 08:51:35 +01:00
|
|
|
auto invalidParamsErrorMessage = [&](const JsonObject ¶ms) {
|
|
|
|
|
return tr("Invalid parameter in \"%1\":\n%2")
|
|
|
|
|
.arg(method, QString::fromUtf8(QJsonDocument(params).toJson(QJsonDocument::Indented)));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto createDefaultResponse = [&]() -> IContent * {
|
|
|
|
|
Response<std::nullptr_t, JsonObject> *response = nullptr;
|
|
|
|
|
if (id.isValid()) {
|
|
|
|
|
response = new Response<std::nullptr_t, JsonObject>(id);
|
|
|
|
|
response->setResult(nullptr);
|
|
|
|
|
}
|
|
|
|
|
return response;
|
2020-02-10 14:28:08 +01:00
|
|
|
};
|
|
|
|
|
|
2021-11-18 08:51:35 +01:00
|
|
|
const bool isRequest = id.isValid();
|
|
|
|
|
IContent *response = nullptr;
|
|
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
if (method == PublishDiagnosticsNotification::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (params.isValid())
|
2019-02-01 14:08:02 +01:00
|
|
|
handleDiagnostics(params);
|
2020-02-10 14:28:08 +01:00
|
|
|
else
|
2021-11-18 08:51:35 +01:00
|
|
|
log(invalidParamsErrorMessage(params));
|
2018-07-13 12:33:46 +02:00
|
|
|
} else if (method == LogMessageNotification::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const LogMessageNotification *>(content)->params().value_or(LogMessageParams());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (params.isValid())
|
2020-12-17 10:24:33 +01:00
|
|
|
log(params);
|
2020-02-10 14:28:08 +01:00
|
|
|
else
|
2021-11-18 08:51:35 +01:00
|
|
|
log(invalidParamsErrorMessage(params));
|
2018-09-12 10:24:02 +02:00
|
|
|
} else if (method == ShowMessageNotification::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const ShowMessageNotification *>(content)->params().value_or(ShowMessageParams());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (params.isValid())
|
2018-07-13 12:33:46 +02:00
|
|
|
log(params);
|
2020-02-10 14:28:08 +01:00
|
|
|
else
|
2021-11-18 08:51:35 +01:00
|
|
|
log(invalidParamsErrorMessage(params));
|
2018-09-12 10:24:02 +02:00
|
|
|
} else if (method == ShowMessageRequest::methodName) {
|
2018-11-14 01:21:20 +01:00
|
|
|
auto request = dynamic_cast<const ShowMessageRequest *>(content);
|
2021-11-18 08:51:35 +01:00
|
|
|
auto showMessageResponse = new ShowMessageRequest::Response(id);
|
2018-09-12 10:24:02 +02:00
|
|
|
auto params = request->params().value_or(ShowMessageRequestParams());
|
2021-02-26 08:29:15 +01:00
|
|
|
if (params.isValid()) {
|
2021-11-18 08:51:35 +01:00
|
|
|
showMessageResponse->setResult(showMessageBox(params));
|
2018-09-12 10:24:02 +02:00
|
|
|
} else {
|
2021-11-18 08:51:35 +01:00
|
|
|
const QString errorMessage = invalidParamsErrorMessage(params);
|
|
|
|
|
log(errorMessage);
|
|
|
|
|
showMessageResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
2018-09-12 10:24:02 +02:00
|
|
|
}
|
2021-11-18 08:51:35 +01:00
|
|
|
response = showMessageResponse;
|
2018-07-13 12:33:46 +02:00
|
|
|
} else if (method == RegisterCapabilityRequest::methodName) {
|
2021-11-18 08:51:35 +01:00
|
|
|
auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(
|
|
|
|
|
RegistrationParams());
|
|
|
|
|
if (params.isValid()) {
|
2021-01-20 12:46:07 +01:00
|
|
|
registerCapabilities(params.registrations());
|
2021-11-18 08:51:35 +01:00
|
|
|
response = createDefaultResponse();
|
|
|
|
|
} else {
|
|
|
|
|
const QString errorMessage = invalidParamsErrorMessage(params);
|
|
|
|
|
log(invalidParamsErrorMessage(params));
|
|
|
|
|
auto registerResponse = new RegisterCapabilityRequest::Response(id);
|
|
|
|
|
registerResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
|
|
|
|
response = registerResponse;
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
} else if (method == UnregisterCapabilityRequest::methodName) {
|
2021-11-18 08:51:35 +01:00
|
|
|
auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(
|
|
|
|
|
UnregistrationParams());
|
|
|
|
|
if (params.isValid()) {
|
2021-02-11 09:32:11 +01:00
|
|
|
unregisterCapabilities(params.unregistrations());
|
2021-11-18 08:51:35 +01:00
|
|
|
response = createDefaultResponse();
|
|
|
|
|
} else {
|
|
|
|
|
const QString errorMessage = invalidParamsErrorMessage(params);
|
|
|
|
|
log(invalidParamsErrorMessage(params));
|
|
|
|
|
auto registerResponse = new UnregisterCapabilityRequest::Response(id);
|
|
|
|
|
registerResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
|
|
|
|
response = registerResponse;
|
|
|
|
|
}
|
2019-01-25 09:48:44 +01:00
|
|
|
} else if (method == ApplyWorkspaceEditRequest::methodName) {
|
2021-11-18 08:51:35 +01:00
|
|
|
auto editResponse = new ApplyWorkspaceEditRequest::Response(id);
|
|
|
|
|
auto params = dynamic_cast<const ApplyWorkspaceEditRequest *>(content)->params().value_or(
|
|
|
|
|
ApplyWorkspaceEditParams());
|
|
|
|
|
if (params.isValid()) {
|
|
|
|
|
ApplyWorkspaceEditResult result;
|
|
|
|
|
result.setApplied(applyWorkspaceEdit(this, params.edit()));
|
|
|
|
|
editResponse->setResult(result);
|
|
|
|
|
} else {
|
|
|
|
|
const QString errorMessage = invalidParamsErrorMessage(params);
|
|
|
|
|
log(errorMessage);
|
|
|
|
|
editResponse->setError(createInvalidParamsError<std::nullptr_t>(errorMessage));
|
|
|
|
|
}
|
|
|
|
|
response = editResponse;
|
2019-03-15 10:09:17 +01:00
|
|
|
} else if (method == WorkSpaceFolderRequest::methodName) {
|
2021-11-18 08:51:35 +01:00
|
|
|
auto workSpaceFolderResponse = new WorkSpaceFolderRequest::Response(id);
|
2019-03-15 10:09:17 +01:00
|
|
|
const QList<ProjectExplorer::Project *> projects
|
|
|
|
|
= ProjectExplorer::SessionManager::projects();
|
|
|
|
|
WorkSpaceFolderResult result;
|
|
|
|
|
if (projects.isEmpty()) {
|
|
|
|
|
result = nullptr;
|
|
|
|
|
} else {
|
|
|
|
|
result = Utils::transform(projects, [](ProjectExplorer::Project *project) {
|
2020-08-12 13:07:14 +02:00
|
|
|
return WorkSpaceFolder(DocumentUri::fromFilePath(project->projectDirectory()),
|
2019-03-15 10:09:17 +01:00
|
|
|
project->displayName());
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-11-18 08:51:35 +01:00
|
|
|
workSpaceFolderResponse->setResult(result);
|
|
|
|
|
response = workSpaceFolderResponse;
|
2021-02-02 09:50:56 +01:00
|
|
|
} else if (method == WorkDoneProgressCreateRequest::methodName) {
|
2021-11-18 08:51:35 +01:00
|
|
|
response = createDefaultResponse();
|
2021-11-02 14:21:50 +01:00
|
|
|
} else if (method == SemanticTokensRefreshRequest::methodName) {
|
|
|
|
|
m_tokenSupport.refresh();
|
2021-11-18 08:51:35 +01:00
|
|
|
response = createDefaultResponse();
|
2021-02-02 09:50:56 +01:00
|
|
|
} else if (method == ProgressNotification::methodName) {
|
|
|
|
|
if (Utils::optional<ProgressParams> params
|
|
|
|
|
= dynamic_cast<const ProgressNotification *>(content)->params()) {
|
|
|
|
|
if (!params->isValid())
|
2021-11-18 08:51:35 +01:00
|
|
|
log(invalidParamsErrorMessage(*params));
|
2021-02-02 09:50:56 +01:00
|
|
|
m_progressManager.handleProgress(*params);
|
2021-04-20 15:46:35 +02:00
|
|
|
if (ProgressManager::isProgressEndMessage(*params))
|
|
|
|
|
emit workDone(params->token());
|
2021-02-02 09:50:56 +01:00
|
|
|
}
|
2021-11-18 08:51:35 +01:00
|
|
|
} else if (isRequest) {
|
|
|
|
|
auto methodNotFoundResponse = new Response<JsonObject, JsonObject>(id);
|
2018-07-13 12:33:46 +02:00
|
|
|
ResponseError<JsonObject> error;
|
|
|
|
|
error.setCode(ResponseError<JsonObject>::MethodNotFound);
|
2021-11-18 08:51:35 +01:00
|
|
|
methodNotFoundResponse->setError(error);
|
|
|
|
|
response = methodNotFoundResponse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we got a request and handled it somewhere above but we missed to generate a response for it
|
|
|
|
|
QTC_ASSERT(!isRequest || response, response = createDefaultResponse());
|
|
|
|
|
|
|
|
|
|
if (response) {
|
|
|
|
|
if (reachable()) {
|
|
|
|
|
sendContent(*response);
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(LOGLSPCLIENT)
|
|
|
|
|
<< QString("Dropped response to request %1 id %2 for unreachable server %3")
|
|
|
|
|
.arg(method, id.toString(), name());
|
|
|
|
|
}
|
|
|
|
|
delete response;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
delete content;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-01 14:08:02 +01:00
|
|
|
void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
|
|
|
|
{
|
|
|
|
|
const DocumentUri &uri = params.uri();
|
|
|
|
|
|
|
|
|
|
const QList<Diagnostic> &diagnostics = params.diagnostics();
|
2021-02-01 14:16:34 +01:00
|
|
|
m_diagnosticManager.setDiagnostics(uri, diagnostics, params.version());
|
2019-10-15 13:48:34 +02:00
|
|
|
if (LanguageClientManager::clientForUri(uri) == this) {
|
2021-06-14 11:47:04 +02:00
|
|
|
m_diagnosticManager.showDiagnostics(uri, m_documentVersions.value(uri.toFilePath()));
|
2021-08-11 11:20:42 +02:00
|
|
|
if (m_autoRequestCodeActions)
|
|
|
|
|
requestCodeActions(uri, diagnostics);
|
2019-10-15 13:48:34 +02:00
|
|
|
}
|
2019-02-01 14:08:02 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-14 14:43:53 +01:00
|
|
|
void Client::sendMessage(const BaseMessage &message)
|
|
|
|
|
{
|
|
|
|
|
LanguageClientManager::logBaseMessage(LspLogMessage::ClientMessage, name(), message);
|
|
|
|
|
m_clientInterface->sendMessage(message);
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-02 12:29:23 +02:00
|
|
|
bool Client::documentUpdatePostponed(const Utils::FilePath &fileName) const
|
2020-06-17 14:15:13 +02:00
|
|
|
{
|
2021-09-14 12:13:56 +02:00
|
|
|
return Utils::contains(m_documentsToUpdate, [fileName](const auto &elem) {
|
|
|
|
|
return elem.first->filePath() == fileName;
|
2020-06-17 14:15:13 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-14 11:47:04 +02:00
|
|
|
int Client::documentVersion(const Utils::FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
return m_documentVersions.value(filePath);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-12 12:00:58 +02:00
|
|
|
void Client::setDocumentChangeUpdateThreshold(int msecs)
|
|
|
|
|
{
|
|
|
|
|
m_documentUpdateTimer.setInterval(msecs);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-18 14:26:08 +02:00
|
|
|
void Client::initializeCallback(const InitializeRequest::Response &initResponse)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_state == InitializeRequested, return);
|
|
|
|
|
if (optional<ResponseError<InitializeError>> error = initResponse.error()) {
|
2021-03-02 12:13:11 +01:00
|
|
|
if (error.value().data().has_value() && error.value().data().value().retry()) {
|
2018-10-24 06:56:14 +02:00
|
|
|
const QString title(tr("Language Server \"%1\" Initialize Error").arg(m_displayName));
|
2018-07-13 12:33:46 +02:00
|
|
|
auto result = QMessageBox::warning(Core::ICore::dialogParent(),
|
|
|
|
|
title,
|
|
|
|
|
error.value().message(),
|
|
|
|
|
QMessageBox::Retry | QMessageBox::Cancel,
|
|
|
|
|
QMessageBox::Retry);
|
|
|
|
|
if (result == QMessageBox::Retry) {
|
|
|
|
|
m_state = Uninitialized;
|
|
|
|
|
initialize();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setError(tr("Initialize error: ") + error.value().message());
|
|
|
|
|
emit finished();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const optional<InitializeResult> &_result = initResponse.result();
|
|
|
|
|
if (!_result.has_value()) {// continue on ill formed result
|
|
|
|
|
log(tr("No initialize result."));
|
|
|
|
|
} else {
|
|
|
|
|
const InitializeResult &result = _result.value();
|
2021-02-26 08:29:15 +01:00
|
|
|
if (!result.isValid()) { // continue on ill formed result
|
2020-02-10 14:28:08 +01:00
|
|
|
log(QJsonDocument(result).toJson(QJsonDocument::Indented) + '\n'
|
2021-02-26 08:29:15 +01:00
|
|
|
+ tr("Initialize result is not valid"));
|
2020-02-10 14:28:08 +01:00
|
|
|
}
|
2021-04-28 09:25:25 +02:00
|
|
|
const Utils::optional<ServerInfo> serverInfo = result.serverInfo();
|
|
|
|
|
if (serverInfo) {
|
|
|
|
|
if (!serverInfo->isValid()) {
|
|
|
|
|
log(QJsonDocument(result).toJson(QJsonDocument::Indented) + '\n'
|
|
|
|
|
+ tr("Server Info is not valid"));
|
|
|
|
|
} else {
|
|
|
|
|
m_serverName = serverInfo->name();
|
|
|
|
|
if (const Utils::optional<QString> version = serverInfo->version())
|
|
|
|
|
m_serverVersion = version.value();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
|
2021-03-02 12:13:11 +01:00
|
|
|
m_serverCapabilities = result.capabilities();
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
2019-09-10 09:37:35 +02:00
|
|
|
|
|
|
|
|
if (auto completionProvider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
|
|
|
|
m_clientProviders.completionAssistProvider)) {
|
|
|
|
|
completionProvider->setTriggerCharacters(
|
|
|
|
|
m_serverCapabilities.completionProvider()
|
|
|
|
|
.value_or(ServerCapabilities::CompletionOptions())
|
2021-01-20 12:46:07 +01:00
|
|
|
.triggerCharacters());
|
2019-09-10 09:37:35 +02:00
|
|
|
}
|
|
|
|
|
if (auto functionHintAssistProvider = qobject_cast<FunctionHintAssistProvider *>(
|
2020-03-25 14:38:26 +01:00
|
|
|
m_clientProviders.functionHintProvider)) {
|
2019-09-10 09:37:35 +02:00
|
|
|
functionHintAssistProvider->setTriggerCharacters(
|
|
|
|
|
m_serverCapabilities.signatureHelpProvider()
|
|
|
|
|
.value_or(ServerCapabilities::SignatureHelpOptions())
|
2021-01-22 09:53:50 +01:00
|
|
|
.triggerCharacters());
|
2019-09-10 09:37:35 +02:00
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
auto tokenProvider = m_serverCapabilities.semanticTokensProvider().value_or(
|
|
|
|
|
SemanticTokensOptions());
|
|
|
|
|
if (tokenProvider.isValid())
|
2021-08-09 14:45:57 +02:00
|
|
|
m_tokenSupport.setLegend(tokenProvider.legend());
|
2019-09-10 09:37:35 +02:00
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized";
|
|
|
|
|
m_state = Initialized;
|
2020-07-13 13:52:27 +02:00
|
|
|
sendContent(InitializeNotification(InitializedParams()));
|
2021-02-02 07:34:15 +01:00
|
|
|
Utils::optional<Utils::variant<bool, WorkDoneProgressOptions>> documentSymbolProvider
|
|
|
|
|
= capabilities().documentSymbolProvider();
|
|
|
|
|
if (documentSymbolProvider.has_value()) {
|
|
|
|
|
if (!Utils::holds_alternative<bool>(*documentSymbolProvider)
|
|
|
|
|
|| Utils::get<bool>(*documentSymbolProvider)) {
|
|
|
|
|
TextEditor::IOutlineWidgetFactory::updateOutline();
|
|
|
|
|
}
|
2019-07-16 12:29:20 +02:00
|
|
|
}
|
2019-09-10 08:03:58 +02:00
|
|
|
|
2021-08-11 12:37:52 +02:00
|
|
|
for (TextEditor::TextDocument *doc : m_postponedDocuments)
|
|
|
|
|
openDocument(doc);
|
|
|
|
|
m_postponedDocuments.clear();
|
2019-09-10 08:03:58 +02:00
|
|
|
|
2019-04-05 10:05:25 +02:00
|
|
|
emit initialized(m_serverCapabilities);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::shutDownCallback(const ShutdownRequest::Response &shutdownResponse)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2022-02-22 07:42:54 +01:00
|
|
|
m_shutdownTimer.stop();
|
2018-07-13 12:33:46 +02:00
|
|
|
QTC_ASSERT(m_state == ShutdownRequested, return);
|
2019-01-31 08:46:23 +01:00
|
|
|
QTC_ASSERT(m_clientInterface, return);
|
2022-02-22 13:20:55 +01:00
|
|
|
if (optional<ShutdownRequest::Response::Error> error = shutdownResponse.error())
|
|
|
|
|
log(*error);
|
2022-02-14 14:43:53 +01:00
|
|
|
// directly send message otherwise the state check of sendContent would fail
|
|
|
|
|
sendMessage(ExitNotification().toBaseMessage());
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown";
|
|
|
|
|
m_state = Shutdown;
|
2022-02-22 07:42:54 +01:00
|
|
|
m_shutdownTimer.start();
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::sendWorkspceFolderChanges() const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2021-02-04 11:31:26 +01:00
|
|
|
if (!reachable())
|
|
|
|
|
return false;
|
2018-07-13 12:33:46 +02:00
|
|
|
if (m_dynamicCapabilities.isRegistered(
|
|
|
|
|
DidChangeWorkspaceFoldersNotification::methodName).value_or(false)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (auto workspace = m_serverCapabilities.workspace()) {
|
|
|
|
|
if (auto folder = workspace.value().workspaceFolders()) {
|
|
|
|
|
if (folder.value().supported().value_or(false)) {
|
|
|
|
|
// holds either the Id for deregistration or whether it is registered
|
|
|
|
|
auto notification = folder.value().changeNotifications().value_or(false);
|
|
|
|
|
return holds_alternative<QString>(notification)
|
|
|
|
|
|| (holds_alternative<bool>(notification) && get<bool>(notification));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-02 13:18:02 +01:00
|
|
|
QTextCursor Client::adjustedCursorForHighlighting(const QTextCursor &cursor,
|
|
|
|
|
TextEditor::TextDocument *doc)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(doc)
|
|
|
|
|
return cursor;
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
} // namespace LanguageClient
|