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
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
#include <coreplugin/idocument.h>
|
|
|
|
|
#include <coreplugin/messagemanager.h>
|
|
|
|
|
#include <languageserverprotocol/diagnostics.h>
|
|
|
|
|
#include <languageserverprotocol/languagefeatures.h>
|
|
|
|
|
#include <languageserverprotocol/messages.h>
|
|
|
|
|
#include <languageserverprotocol/workspace.h>
|
2019-05-16 11:09:55 +02:00
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
|
|
|
|
#include <texteditor/codeassist/documentcontentcompletion.h>
|
2019-06-12 12:55:06 +02:00
|
|
|
#include <texteditor/syntaxhighlighter.h>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
|
#include <texteditor/texteditor.h>
|
2019-06-12 12:55:06 +02:00
|
|
|
#include <texteditor/texteditorsettings.h>
|
2019-02-01 14:08:02 +01:00
|
|
|
#include <texteditor/textmark.h>
|
2019-07-16 12:29:20 +02:00
|
|
|
#include <texteditor/ioutlinewidget.h>
|
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>
|
2018-07-13 12:33:46 +02:00
|
|
|
#include <utils/synchronousprocess.h>
|
2019-02-01 14:08:02 +01:00
|
|
|
#include <utils/utilsicons.h>
|
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-02-01 14:08:02 +01:00
|
|
|
class TextMark : public TextEditor::TextMark
|
|
|
|
|
{
|
|
|
|
|
public:
|
2019-10-15 13:48:34 +02:00
|
|
|
TextMark(const Utils::FilePath &fileName, const Diagnostic &diag, const Core::Id &clientId)
|
|
|
|
|
: TextEditor::TextMark(fileName, diag.range().start().line() + 1, clientId)
|
2019-01-29 13:20:58 +01:00
|
|
|
, m_diagnostic(diag)
|
2019-02-01 14:08:02 +01:00
|
|
|
{
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
setLineAnnotation(diag.message());
|
|
|
|
|
setToolTip(diag.message());
|
|
|
|
|
const bool isError
|
|
|
|
|
= diag.severity().value_or(DiagnosticSeverity::Hint) == DiagnosticSeverity::Error;
|
|
|
|
|
setColor(isError ? Theme::CodeModel_Error_TextMarkColor
|
|
|
|
|
: Theme::CodeModel_Warning_TextMarkColor);
|
|
|
|
|
|
|
|
|
|
setIcon(isError ? Icons::CODEMODEL_ERROR.icon()
|
|
|
|
|
: Icons::CODEMODEL_WARNING.icon());
|
|
|
|
|
}
|
2019-01-29 13:20:58 +01:00
|
|
|
|
|
|
|
|
const Diagnostic &diagnostic() const { return m_diagnostic; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const Diagnostic m_diagnostic;
|
2019-02-01 14:08:02 +01:00
|
|
|
};
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
Client::Client(BaseClientInterface *clientInterface)
|
2018-07-13 12:33:46 +02:00
|
|
|
: m_id(Core::Id::fromString(QUuid::createUuid().toString()))
|
2019-01-31 08:46:23 +01:00
|
|
|
, m_clientInterface(clientInterface)
|
2019-04-04 14:36:28 +02:00
|
|
|
, m_documentSymbolCache(this)
|
2019-05-15 11:19:31 +02:00
|
|
|
, m_hoverHandler(this)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
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);
|
|
|
|
|
|
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);
|
2019-06-12 12:55:06 +02:00
|
|
|
connect(TextEditor::TextEditorSettings::instance(),
|
|
|
|
|
&TextEditor::TextEditorSettings::fontSettingsChanged,
|
|
|
|
|
this,
|
|
|
|
|
&Client::rehighlight);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
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
|
2019-09-10 08:03:36 +02:00
|
|
|
for (TextDocument *document : m_resetAssistProvider.keys())
|
|
|
|
|
resetAssistProviders(document);
|
2019-01-25 09:48:44 +01:00
|
|
|
for (Core::IEditor * editor : Core::DocumentModel::editorsForOpenedDocuments()) {
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
2019-02-01 14:08:02 +01:00
|
|
|
for (const DocumentUri &uri : m_diagnostics.keys())
|
|
|
|
|
removeDiagnostics(uri);
|
2019-06-12 12:55:06 +02:00
|
|
|
for (const DocumentUri &uri : m_highlights.keys()) {
|
2019-10-01 09:11:02 +02:00
|
|
|
if (TextDocument *doc = TextDocument::textDocumentForFilePath(uri.toFilePath())) {
|
2019-06-12 12:55:06 +02:00
|
|
|
if (TextEditor::SyntaxHighlighter *highlighter = doc->syntaxHighlighter())
|
|
|
|
|
highlighter->clearAllExtraFormats();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-02 10:49:23 +02:00
|
|
|
updateEditorToolBar(m_openedDocument.keys());
|
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);
|
|
|
|
|
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);
|
|
|
|
|
documentCapabilities.setDocumentSymbol(symbolCapabilities);
|
|
|
|
|
|
2019-06-12 12:55:06 +02:00
|
|
|
TextDocumentClientCapabilities::SemanticHighlightingCapabilities semanticHighlight;
|
|
|
|
|
semanticHighlight.setSemanticHighlighting(true);
|
|
|
|
|
documentCapabilities.setSemanticHighlightingCapabilities(semanticHighlight);
|
|
|
|
|
|
2019-03-15 14:53:36 +01:00
|
|
|
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;
|
|
|
|
|
completionItemCapbilities.setSnippetSupport(false);
|
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;
|
|
|
|
|
hover.setContentFormat({MarkupKind::plaintext});
|
|
|
|
|
hover.setDynamicRegistration(true);
|
|
|
|
|
documentCapabilities.setHover(hover);
|
|
|
|
|
|
2019-03-26 09:29:55 +01:00
|
|
|
documentCapabilities.setReferences(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setDocumentHighlight(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setDefinition(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setTypeDefinition(allowDynamicRegistration);
|
|
|
|
|
documentCapabilities.setImplementation(allowDynamicRegistration);
|
2019-03-15 14:53:36 +01:00
|
|
|
capabilities.setTextDocument(documentCapabilities);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
auto initRequest = new InitializeRequest();
|
2019-05-15 14:55:55 +02:00
|
|
|
auto params = initRequest->params().value_or(InitializeParams());
|
|
|
|
|
params.setCapabilities(generateClientCapabilities());
|
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()));
|
2018-07-13 12:33:46 +02:00
|
|
|
params.setWorkSpaceFolders(Utils::transform(SessionManager::projects(), [](Project *pro){
|
|
|
|
|
return WorkSpaceFolder(pro->projectDirectory().toString(), pro->displayName());
|
|
|
|
|
}));
|
|
|
|
|
}
|
2019-05-15 14:55:55 +02:00
|
|
|
initRequest->setParams(params);
|
2018-11-20 07:45:22 +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
|
|
|
});
|
|
|
|
|
// directly send data otherwise the state check would fail;
|
|
|
|
|
initRequest->registerResponseHandler(&m_responseHandlers);
|
2019-01-31 08:46:23 +01:00
|
|
|
m_clientInterface->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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
Client::State Client::state() const
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
return m_state;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2018-10-10 14:26:57 +02:00
|
|
|
if (!isSupportedDocument(document))
|
2019-09-10 08:03:58 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_openedDocument[document] = document->plainText();
|
|
|
|
|
if (m_state != Initialized)
|
|
|
|
|
return;
|
|
|
|
|
|
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());
|
2018-07-13 12:33:46 +02:00
|
|
|
if (option.isValid(nullptr)
|
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
|
|
|
}
|
|
|
|
|
}
|
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());
|
2019-09-11 11:15:39 +02:00
|
|
|
item.setVersion(document->document()->revision());
|
2018-07-13 12:33:46 +02:00
|
|
|
sendContent(DidOpenTextDocumentNotification(DidOpenTextDocumentParams(item)));
|
2019-03-26 13:48:06 +01:00
|
|
|
|
2019-09-10 08:03:58 +02:00
|
|
|
if (LanguageClientManager::clientForDocument(document) == this)
|
|
|
|
|
activateDocument(document);
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::sendContent(const IContent &content)
|
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);
|
|
|
|
|
content.registerResponseHandler(&m_responseHandlers);
|
|
|
|
|
QString error;
|
|
|
|
|
if (!QTC_GUARD(content.isValid(&error)))
|
|
|
|
|
Core::MessageManager::write(error);
|
2019-01-31 08:46:23 +01:00
|
|
|
m_clientInterface->sendMessage(content.toBaseMessage());
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::sendContent(const DocumentUri &uri, const IContent &content)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
if (!Utils::anyOf(m_openedDocument.keys(), [uri](TextEditor::TextDocument *documnent) {
|
2019-09-11 14:34:20 +02:00
|
|
|
return uri.toFilePath() == documnent->filePath();
|
2019-09-11 14:31:20 +02:00
|
|
|
})) {
|
|
|
|
|
sendContent(content);
|
|
|
|
|
}
|
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);
|
|
|
|
|
sendContent(CancelRequest(CancelParameter(id)));
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 11:15:39 +02:00
|
|
|
void Client::closeDocument(TextEditor::TextDocument *document)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
if (m_openedDocument.remove(document) == 0)
|
|
|
|
|
return;
|
2019-09-11 14:34:20 +02:00
|
|
|
const DocumentUri &uri = DocumentUri::fromFilePath(document->filePath());
|
2019-07-24 13:40:12 +02:00
|
|
|
const DidCloseTextDocumentParams params(TextDocumentIdentifier{uri});
|
2019-06-12 12:55:06 +02:00
|
|
|
m_highlights[uri].clear();
|
|
|
|
|
sendContent(uri, DidCloseTextDocumentNotification(params));
|
2019-09-10 08:03:58 +02:00
|
|
|
deactivateDocument(document);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::activateDocument(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
auto uri = DocumentUri::fromFilePath(document->filePath());
|
|
|
|
|
showDiagnostics(uri);
|
|
|
|
|
SemanticHighligtingSupport::applyHighlight(document, m_highlights.value(uri), capabilities());
|
|
|
|
|
// only replace the assist provider if the completion provider is the default one or null
|
|
|
|
|
if (!document->completionAssistProvider()
|
|
|
|
|
|| qobject_cast<TextEditor::DocumentContentCompletionProvider *>(
|
|
|
|
|
document->completionAssistProvider())) {
|
|
|
|
|
m_resetAssistProvider[document] = {document->completionAssistProvider(),
|
|
|
|
|
document->functionHintAssistProvider(),
|
|
|
|
|
document->quickFixAssistProvider()};
|
|
|
|
|
document->setCompletionAssistProvider(m_clientProviders.completionAssistProvider);
|
|
|
|
|
document->setFunctionHintAssistProvider(m_clientProviders.functionHintProvider);
|
|
|
|
|
document->setQuickFixAssistProvider(m_clientProviders.quickFixAssistProvider);
|
|
|
|
|
}
|
|
|
|
|
for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) {
|
|
|
|
|
updateEditorToolBar(editor);
|
|
|
|
|
if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
|
2019-11-06 10:14:13 +01:00
|
|
|
textEditor->editorWidget()->addHoverHandler(&m_hoverHandler);
|
2019-09-10 08:03:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::deactivateDocument(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
hideDiagnostics(document);
|
2019-09-11 11:15:39 +02:00
|
|
|
resetAssistProviders(document);
|
2019-09-10 08:03:58 +02:00
|
|
|
if (TextEditor::SyntaxHighlighter *highlighter = document->syntaxHighlighter())
|
|
|
|
|
highlighter->clearAllExtraFormats();
|
2019-11-06 10:14:13 +01:00
|
|
|
for (Core::IEditor *editor : Core::DocumentModel::editorsForDocument(document)) {
|
|
|
|
|
if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor))
|
|
|
|
|
textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-11 14:31:20 +02:00
|
|
|
bool Client::documentOpen(TextEditor::TextDocument *document) const
|
2019-03-26 13:48:06 +01:00
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
return m_openedDocument.contains(document);
|
2019-03-26 13:48:06 +01:00
|
|
|
}
|
|
|
|
|
|
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());
|
|
|
|
|
if (option.isValid(nullptr)) {
|
|
|
|
|
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;
|
|
|
|
|
bool sendMessage = true;
|
|
|
|
|
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));
|
|
|
|
|
if (option.isValid(nullptr)) {
|
|
|
|
|
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
|
|
|
{
|
2019-09-11 14:31:20 +02:00
|
|
|
if (!m_openedDocument.contains(document))
|
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)) {
|
|
|
|
|
syncKind = registered.value() ? TextDocumentSyncKind::None : TextDocumentSyncKind::Full;
|
|
|
|
|
if (syncKind != TextDocumentSyncKind::None) {
|
|
|
|
|
const TextDocumentChangeRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(method).toObject());
|
|
|
|
|
syncKind = option.isValid(nullptr) ? option.syncKind() : syncKind;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
auto textDocument = qobject_cast<TextEditor::TextDocument *>(document);
|
2019-01-25 09:48:44 +01:00
|
|
|
|
2019-09-11 14:34:20 +02:00
|
|
|
const auto uri = DocumentUri::fromFilePath(document->filePath());
|
2019-06-12 12:55:06 +02:00
|
|
|
m_highlights[uri].clear();
|
2018-07-13 12:33:46 +02:00
|
|
|
if (syncKind != TextDocumentSyncKind::None) {
|
|
|
|
|
VersionedTextDocumentIdentifier docId(uri);
|
|
|
|
|
docId.setVersion(textDocument ? textDocument->document()->revision() : 0);
|
2019-04-02 10:49:23 +02:00
|
|
|
DidChangeTextDocumentParams params;
|
|
|
|
|
params.setTextDocument(docId);
|
|
|
|
|
if (syncKind == TextDocumentSyncKind::Incremental) {
|
|
|
|
|
DidChangeTextDocumentParams::TextDocumentContentChangeEvent change;
|
2019-09-11 14:31:20 +02:00
|
|
|
QTextDocument oldDoc(m_openedDocument[document]);
|
2019-04-02 10:49:23 +02:00
|
|
|
QTextCursor cursor(&oldDoc);
|
|
|
|
|
cursor.setPosition(position + charsRemoved);
|
|
|
|
|
cursor.setPosition(position, QTextCursor::KeepAnchor);
|
|
|
|
|
change.setRange(Range(cursor));
|
2019-07-18 10:52:27 +02:00
|
|
|
change.setRangeLength(cursor.selectionEnd() - cursor.selectionStart());
|
2019-04-02 10:49:23 +02:00
|
|
|
change.setText(document->textAt(position, charsAdded));
|
|
|
|
|
params.setContentChanges({change});
|
|
|
|
|
} else {
|
|
|
|
|
params.setContentChanges({document->plainText()});
|
|
|
|
|
}
|
2019-09-11 14:31:20 +02:00
|
|
|
m_openedDocument[document] = document->plainText();
|
2018-07-13 12:33:46 +02:00
|
|
|
sendContent(DidChangeTextDocumentNotification(params));
|
|
|
|
|
}
|
2019-01-25 09:48:44 +01:00
|
|
|
|
|
|
|
|
if (textDocument) {
|
|
|
|
|
using namespace TextEditor;
|
2019-03-12 13:46:31 +01:00
|
|
|
for (BaseTextEditor *editor : BaseTextEditor::textEditorsForDocument(textDocument))
|
2019-01-25 09:48:44 +01:00
|
|
|
if (TextEditorWidget *widget = editor->editorWidget())
|
|
|
|
|
widget->setRefactorMarkers(RefactorMarker::filterOutType(widget->refactorMarkers(), id()));
|
|
|
|
|
}
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-28 08:16:19 +01:00
|
|
|
template <typename Request>
|
2019-01-31 12:15:43 +01:00
|
|
|
static bool sendTextDocumentPositionParamsRequest(Client *client,
|
2018-11-28 08:16:19 +01:00
|
|
|
const Request &request,
|
|
|
|
|
const DynamicCapabilities &dynamicCapabilities,
|
|
|
|
|
const optional<bool> &serverCapability)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
2018-11-28 08:16:19 +01:00
|
|
|
if (!request.isValid(nullptr))
|
|
|
|
|
return false;
|
|
|
|
|
const DocumentUri uri = request.params().value().textDocument().uri();
|
|
|
|
|
const bool supportedFile = client->isSupportedUri(uri);
|
|
|
|
|
bool sendMessage = dynamicCapabilities.isRegistered(Request::methodName).value_or(false);
|
2018-07-13 12:33:46 +02:00
|
|
|
if (sendMessage) {
|
2018-11-28 08:16:19 +01:00
|
|
|
const TextDocumentRegistrationOptions option(dynamicCapabilities.option(Request::methodName));
|
2018-07-13 12:33:46 +02:00
|
|
|
if (option.isValid(nullptr))
|
2019-05-28 13:49:26 +02:00
|
|
|
sendMessage = option.filterApplies(FilePath::fromString(QUrl(uri).adjusted(QUrl::PreferLocalFile).toString()));
|
2018-11-28 08:16:19 +01:00
|
|
|
else
|
|
|
|
|
sendMessage = supportedFile;
|
2018-07-13 12:33:46 +02:00
|
|
|
} else {
|
2018-11-28 08:16:19 +01:00
|
|
|
sendMessage = serverCapability.value_or(sendMessage) && supportedFile;
|
2018-07-13 12:33:46 +02:00
|
|
|
}
|
|
|
|
|
if (sendMessage)
|
2018-11-28 08:16:19 +01:00
|
|
|
client->sendContent(request);
|
2018-07-13 12:33:46 +02:00
|
|
|
return sendMessage;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::findLinkAt(GotoDefinitionRequest &request)
|
2018-11-28 08:16:19 +01:00
|
|
|
{
|
|
|
|
|
return LanguageClient::sendTextDocumentPositionParamsRequest(
|
|
|
|
|
this, request, m_dynamicCapabilities, m_serverCapabilities.definitionProvider());
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::findUsages(FindReferencesRequest &request)
|
2018-11-28 08:16:19 +01:00
|
|
|
{
|
|
|
|
|
return LanguageClient::sendTextDocumentPositionParamsRequest(
|
|
|
|
|
this, request, m_dynamicCapabilities, m_serverCapabilities.referencesProvider());
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation &info)
|
|
|
|
|
{
|
|
|
|
|
if (!info.isValid(nullptr))
|
|
|
|
|
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
|
|
|
{
|
2019-09-11 14:34:20 +02:00
|
|
|
const auto uri = DocumentUri::fromFilePath(widget->textDocument()->filePath());
|
2018-07-13 12:33:46 +02:00
|
|
|
if (m_dynamicCapabilities.isRegistered(DocumentHighlightsRequest::methodName).value_or(false)) {
|
|
|
|
|
TextDocumentRegistrationOptions option(
|
|
|
|
|
m_dynamicCapabilities.option(DocumentHighlightsRequest::methodName));
|
|
|
|
|
if (!option.filterApplies(widget->textDocument()->filePath()))
|
|
|
|
|
return;
|
|
|
|
|
} else if (!m_serverCapabilities.documentHighlightProvider().value_or(false)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto runningRequest = m_highlightRequests.find(uri);
|
|
|
|
|
if (runningRequest != m_highlightRequests.end())
|
|
|
|
|
cancelRequest(runningRequest.value());
|
|
|
|
|
|
|
|
|
|
DocumentHighlightsRequest request(TextDocumentPositionParams(uri, widget->textCursor()));
|
|
|
|
|
request.setResponseCallback(
|
|
|
|
|
[widget = QPointer<TextEditor::TextEditorWidget>(widget), this, uri]
|
2018-11-20 07:45:22 +01:00
|
|
|
(DocumentHighlightsRequest::Response response)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
m_highlightRequests.remove(uri);
|
|
|
|
|
if (!widget)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QList<QTextEdit::ExtraSelection> selections;
|
|
|
|
|
const DocumentHighlightsResult result = response.result().value_or(DocumentHighlightsResult());
|
|
|
|
|
if (!holds_alternative<QList<DocumentHighlight>>(result)) {
|
|
|
|
|
widget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QTextCharFormat &format =
|
|
|
|
|
widget->textDocument()->fontSettings().toTextCharFormat(TextEditor::C_OCCURRENCES);
|
|
|
|
|
QTextDocument *document = widget->document();
|
|
|
|
|
for (const auto &highlight : get<QList<DocumentHighlight>>(result)) {
|
|
|
|
|
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(TextEditor::TextEditorWidget::CodeSemanticsSelection, selections);
|
|
|
|
|
});
|
|
|
|
|
m_highlightRequests[uri] = request.id();
|
|
|
|
|
sendContent(request);
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
codeActionParams.setTextDocument(uri);
|
|
|
|
|
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());
|
|
|
|
|
if (option.isValid(nullptr) && !option.filterApplies(fileName))
|
|
|
|
|
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
|
|
|
{
|
|
|
|
|
using CommandOptions = LanguageServerProtocol::ServerCapabilities::ExecuteCommandOptions;
|
|
|
|
|
const QString method(ExecuteCommandRequest::methodName);
|
|
|
|
|
if (Utils::optional<bool> registered = m_dynamicCapabilities.isRegistered(method)) {
|
|
|
|
|
if (!registered.value())
|
|
|
|
|
return;
|
|
|
|
|
const CommandOptions option(m_dynamicCapabilities.option(method).toObject());
|
|
|
|
|
if (option.isValid(nullptr) && !option.commands().isEmpty() && !option.commands().contains(command.command()))
|
|
|
|
|
return;
|
|
|
|
|
} else if (Utils::optional<CommandOptions> option = m_serverCapabilities.executeCommandProvider()) {
|
|
|
|
|
if (option->isValid(nullptr) && !option->commands().isEmpty() && !option->commands().contains(command.command()))
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ExecuteCommandRequest request((ExecuteCommandParams(command)));
|
|
|
|
|
sendContent(request);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-15 11:25:48 +01:00
|
|
|
const ProjectExplorer::Project *Client::project() const
|
|
|
|
|
{
|
|
|
|
|
return m_project;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::setCurrentProject(ProjectExplorer::Project *project)
|
|
|
|
|
{
|
|
|
|
|
m_project = project;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
event.setAdded({WorkSpaceFolder(project->projectDirectory().toString(), project->displayName())});
|
|
|
|
|
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
|
|
|
{
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
if (!sendWorkspceFolderChanges())
|
|
|
|
|
return;
|
|
|
|
|
WorkspaceFoldersChangeEvent event;
|
2019-03-15 11:25:48 +01:00
|
|
|
event.setRemoved(
|
|
|
|
|
{WorkSpaceFolder(project->projectDirectory().toString(), 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::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
|
|
|
}
|
|
|
|
|
|
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
|
|
|
{
|
2019-09-11 14:34:20 +02:00
|
|
|
return m_languagFilter.isSupported(uri.toFilePath(),
|
|
|
|
|
Utils::mimeTypeForFile(uri.toFilePath().fileName()).name());
|
2018-11-28 08:16:19 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::needsRestart(const BaseSettings *settings) const
|
2018-09-18 10:43:17 +02:00
|
|
|
{
|
2018-10-19 07:05:46 +02:00
|
|
|
QTC_ASSERT(settings, return false);
|
|
|
|
|
return m_languagFilter.mimeTypes != settings->m_languageFilter.mimeTypes
|
|
|
|
|
|| m_languagFilter.filePattern != settings->m_languageFilter.filePattern;
|
2018-09-18 10:43:17 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-29 13:20:58 +01:00
|
|
|
QList<Diagnostic> Client::diagnosticsAt(const DocumentUri &uri, const Range &range) const
|
|
|
|
|
{
|
|
|
|
|
QList<Diagnostic> diagnostics;
|
2019-10-15 13:48:34 +02:00
|
|
|
for (const Diagnostic &diagnostic : m_diagnostics[uri]) {
|
2019-01-29 13:20:58 +01:00
|
|
|
if (diagnostic.range().overlaps(range))
|
|
|
|
|
diagnostics << diagnostic;
|
|
|
|
|
}
|
|
|
|
|
return diagnostics;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::start()
|
2019-01-31 08:46:23 +01:00
|
|
|
{
|
|
|
|
|
return m_clientInterface->start();
|
|
|
|
|
}
|
|
|
|
|
|
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_openedDocument.clear();
|
|
|
|
|
m_serverCapabilities = ServerCapabilities();
|
|
|
|
|
m_dynamicCapabilities.reset();
|
2019-03-15 11:25:48 +01:00
|
|
|
m_project = nullptr;
|
2019-02-01 14:08:02 +01:00
|
|
|
for (const DocumentUri &uri : m_diagnostics.keys())
|
|
|
|
|
removeDiagnostics(uri);
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::handleMessage(const BaseMessage &message)
|
2019-01-31 08:46:23 +01:00
|
|
|
{
|
|
|
|
|
if (auto handler = m_contentHandler[message.mimeType]) {
|
|
|
|
|
QString parseError;
|
|
|
|
|
handler(message.content, message.codec, parseError,
|
|
|
|
|
[this](MessageId id, const QByteArray &content, QTextCodec *codec){
|
|
|
|
|
this->handleResponse(id, content, codec);
|
|
|
|
|
},
|
|
|
|
|
[this](const QString &method, MessageId id, const IContent *content){
|
|
|
|
|
this->handleMethod(method, id, content);
|
|
|
|
|
});
|
|
|
|
|
if (!parseError.isEmpty())
|
|
|
|
|
log(parseError);
|
|
|
|
|
} else {
|
|
|
|
|
log(tr("Cannot handle content of type: %1").arg(QLatin1String(message.mimeType)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 13:40:12 +02:00
|
|
|
void Client::showDiagnostics(Core::IDocument *doc)
|
|
|
|
|
{
|
2019-09-11 14:34:20 +02:00
|
|
|
showDiagnostics(DocumentUri::fromFilePath(doc->filePath()));
|
2019-07-24 13:40:12 +02:00
|
|
|
}
|
|
|
|
|
|
2019-09-11 11:15:39 +02:00
|
|
|
void Client::hideDiagnostics(TextEditor::TextDocument *doc)
|
2019-07-24 13:40:12 +02:00
|
|
|
{
|
2019-09-10 08:03:58 +02:00
|
|
|
if (!doc)
|
|
|
|
|
return;
|
2019-10-15 13:48:34 +02:00
|
|
|
qDeleteAll(Utils::filtered(doc->marks(), Utils::equal(&TextEditor::TextMark::category, id())));
|
2019-07-24 13:40:12 +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-03-11 07:33:07 +01:00
|
|
|
const BaseClientInterface *Client::clientInterface() const
|
|
|
|
|
{
|
|
|
|
|
return m_clientInterface.data();
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::log(const ShowMessageParams &message,
|
2018-09-12 08:14:25 +02:00
|
|
|
Core::MessageManager::PrintToOutputPaneFlag flag)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
log(message.toString(), flag);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::showMessageBox(const ShowMessageRequestParams &message, const MessageId &id)
|
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);
|
|
|
|
|
}
|
|
|
|
|
box->setModal(true);
|
|
|
|
|
connect(box, &QMessageBox::finished, this, [=]{
|
2019-03-15 10:05:40 +01:00
|
|
|
ShowMessageRequest::Response response(id);
|
2018-09-12 10:24:02 +02:00
|
|
|
const MessageActionItem &item = itemForButton.value(box->clickedButton());
|
|
|
|
|
response.setResult(item.isValid(nullptr) ? LanguageClientValue<MessageActionItem>(item)
|
|
|
|
|
: LanguageClientValue<MessageActionItem>());
|
|
|
|
|
sendContent(response);
|
|
|
|
|
});
|
|
|
|
|
box->show();
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-01 14:08:02 +01:00
|
|
|
void Client::showDiagnostics(const DocumentUri &uri)
|
|
|
|
|
{
|
2019-10-15 13:48:34 +02:00
|
|
|
const FilePath &filePath = uri.toFilePath();
|
|
|
|
|
if (TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFilePath(
|
|
|
|
|
uri.toFilePath())) {
|
|
|
|
|
for (const Diagnostic &diagnostic : m_diagnostics.value(uri))
|
|
|
|
|
doc->addMark(new TextMark(filePath, diagnostic, id()));
|
2019-02-01 14:08:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::removeDiagnostics(const DocumentUri &uri)
|
|
|
|
|
{
|
2019-10-15 13:48:34 +02:00
|
|
|
hideDiagnostics(TextEditor::TextDocument::textDocumentForFilePath(uri.toFilePath()));
|
|
|
|
|
m_diagnostics.remove(uri);
|
2019-02-01 14:08:02 +01:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 08:03:36 +02:00
|
|
|
void Client::resetAssistProviders(TextEditor::TextDocument *document)
|
|
|
|
|
{
|
|
|
|
|
const AssistProviders providers = m_resetAssistProvider.take(document);
|
2019-09-20 07:40:51 +02:00
|
|
|
if (document->completionAssistProvider() == m_clientProviders.completionAssistProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setCompletionAssistProvider(providers.completionAssistProvider);
|
2019-09-20 07:40:51 +02:00
|
|
|
if (document->functionHintAssistProvider() == m_clientProviders.functionHintProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setFunctionHintAssistProvider(providers.functionHintProvider);
|
2019-09-20 07:40:51 +02:00
|
|
|
if (document->quickFixAssistProvider() == m_clientProviders.quickFixAssistProvider)
|
2019-09-10 08:03:36 +02:00
|
|
|
document->setQuickFixAssistProvider(providers.quickFixAssistProvider);
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
void Client::handleMethod(const QString &method, MessageId id, const IContent *content)
|
2018-07-13 12:33:46 +02:00
|
|
|
{
|
|
|
|
|
QStringList error;
|
|
|
|
|
bool paramsValid = true;
|
|
|
|
|
if (method == PublishDiagnosticsNotification::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const PublishDiagnosticsNotification *>(content)->params().value_or(PublishDiagnosticsParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid)
|
2019-02-01 14:08:02 +01:00
|
|
|
handleDiagnostics(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());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
2018-09-12 10:24:02 +02:00
|
|
|
if (paramsValid)
|
|
|
|
|
log(params, Core::MessageManager::Flash);
|
2019-06-12 12:55:06 +02:00
|
|
|
} else if (method == SemanticHighlightNotification::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const SemanticHighlightNotification *>(content)->params().value_or(SemanticHighlightingParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid)
|
|
|
|
|
handleSemanticHighlight(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());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
2018-07-13 12:33:46 +02:00
|
|
|
if (paramsValid)
|
|
|
|
|
log(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);
|
2018-09-12 10:24:02 +02:00
|
|
|
auto params = request->params().value_or(ShowMessageRequestParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid) {
|
|
|
|
|
showMessageBox(params, request->id());
|
|
|
|
|
} else {
|
2019-03-15 10:05:40 +01:00
|
|
|
ShowMessageRequest::Response response(request->id());
|
2019-01-07 13:24:01 +01:00
|
|
|
ResponseError<std::nullptr_t> error;
|
2018-09-12 10:24:02 +02:00
|
|
|
const QString errorMessage =
|
|
|
|
|
QString("Could not parse ShowMessageRequest parameter of '%1': \"%2\"")
|
|
|
|
|
.arg(request->id().toString(),
|
|
|
|
|
QString::fromUtf8(QJsonDocument(params).toJson()));
|
|
|
|
|
error.setMessage(errorMessage);
|
|
|
|
|
response.setError(error);
|
|
|
|
|
sendContent(response);
|
|
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
} else if (method == RegisterCapabilityRequest::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const RegisterCapabilityRequest *>(content)->params().value_or(RegistrationParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid)
|
|
|
|
|
m_dynamicCapabilities.registerCapability(params.registrations());
|
|
|
|
|
} else if (method == UnregisterCapabilityRequest::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const UnregisterCapabilityRequest *>(content)->params().value_or(UnregistrationParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid)
|
|
|
|
|
m_dynamicCapabilities.unregisterCapability(params.unregistrations());
|
2019-01-25 09:48:44 +01:00
|
|
|
} else if (method == ApplyWorkspaceEditRequest::methodName) {
|
|
|
|
|
auto params = dynamic_cast<const ApplyWorkspaceEditRequest *>(content)->params().value_or(ApplyWorkspaceEditParams());
|
|
|
|
|
paramsValid = params.isValid(&error);
|
|
|
|
|
if (paramsValid)
|
|
|
|
|
applyWorkspaceEdit(params.edit());
|
2019-03-15 10:09:17 +01:00
|
|
|
} else if (method == WorkSpaceFolderRequest::methodName) {
|
|
|
|
|
WorkSpaceFolderRequest::Response response(dynamic_cast<const WorkSpaceFolderRequest *>(content)->id());
|
|
|
|
|
const QList<ProjectExplorer::Project *> projects
|
|
|
|
|
= ProjectExplorer::SessionManager::projects();
|
|
|
|
|
WorkSpaceFolderResult result;
|
|
|
|
|
if (projects.isEmpty()) {
|
|
|
|
|
result = nullptr;
|
|
|
|
|
} else {
|
|
|
|
|
result = Utils::transform(projects, [](ProjectExplorer::Project *project) {
|
|
|
|
|
return WorkSpaceFolder(project->projectDirectory().toString(),
|
|
|
|
|
project->displayName());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
response.setResult(result);
|
|
|
|
|
sendContent(response);
|
2018-07-13 12:33:46 +02:00
|
|
|
} else if (id.isValid(&error)) {
|
2019-03-15 10:05:40 +01:00
|
|
|
Response<JsonObject, JsonObject> response(id);
|
2018-07-13 12:33:46 +02:00
|
|
|
ResponseError<JsonObject> error;
|
|
|
|
|
error.setCode(ResponseError<JsonObject>::MethodNotFound);
|
|
|
|
|
response.setError(error);
|
|
|
|
|
sendContent(response);
|
|
|
|
|
}
|
|
|
|
|
std::reverse(error.begin(), error.end());
|
|
|
|
|
if (!paramsValid) {
|
|
|
|
|
log(tr("Invalid parameter in \"%1\": %2").arg(method, error.join("->")),
|
|
|
|
|
Core::MessageManager::Flash);
|
|
|
|
|
}
|
|
|
|
|
delete content;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-01 14:08:02 +01:00
|
|
|
void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
|
|
|
|
{
|
|
|
|
|
const DocumentUri &uri = params.uri();
|
|
|
|
|
|
|
|
|
|
removeDiagnostics(uri);
|
|
|
|
|
const QList<Diagnostic> &diagnostics = params.diagnostics();
|
2019-10-15 13:48:34 +02:00
|
|
|
m_diagnostics[uri] = diagnostics;
|
|
|
|
|
if (LanguageClientManager::clientForUri(uri) == this) {
|
|
|
|
|
showDiagnostics(uri);
|
2019-09-10 08:03:58 +02:00
|
|
|
requestCodeActions(uri, diagnostics);
|
2019-10-15 13:48:34 +02:00
|
|
|
}
|
2019-02-01 14:08:02 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-12 12:55:06 +02:00
|
|
|
void Client::handleSemanticHighlight(const SemanticHighlightingParams ¶ms)
|
|
|
|
|
{
|
|
|
|
|
const DocumentUri &uri = params.textDocument().uri();
|
|
|
|
|
m_highlights[uri].clear();
|
|
|
|
|
const LanguageClientValue<int> &version = params.textDocument().version();
|
2019-10-01 09:11:02 +02:00
|
|
|
TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFilePath(
|
2019-09-11 14:34:20 +02:00
|
|
|
uri.toFilePath());
|
2019-06-12 12:55:06 +02:00
|
|
|
|
2019-09-10 08:03:58 +02:00
|
|
|
if (!doc || LanguageClientManager::clientForDocument(doc) != this
|
|
|
|
|
|| (!version.isNull() && doc->document()->revision() != version.value())) {
|
2019-06-12 12:55:06 +02:00
|
|
|
return;
|
2019-09-10 08:03:58 +02:00
|
|
|
}
|
2019-06-12 12:55:06 +02:00
|
|
|
|
|
|
|
|
const TextEditor::HighlightingResults results = SemanticHighligtingSupport::generateResults(
|
|
|
|
|
params.lines());
|
|
|
|
|
|
|
|
|
|
m_highlights[uri] = results;
|
|
|
|
|
|
|
|
|
|
SemanticHighligtingSupport::applyHighlight(doc, results, capabilities());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Client::rehighlight()
|
|
|
|
|
{
|
|
|
|
|
using namespace TextEditor;
|
|
|
|
|
for (auto it = m_highlights.begin(), end = m_highlights.end(); it != end; ++it) {
|
2019-10-01 09:11:02 +02:00
|
|
|
if (TextDocument *doc = TextDocument::textDocumentForFilePath(it.key().toFilePath())) {
|
2019-09-10 08:03:58 +02:00
|
|
|
if (LanguageClientManager::clientForDocument(doc) == this)
|
|
|
|
|
SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities());
|
|
|
|
|
}
|
2019-06-12 12:55:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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()) {
|
|
|
|
|
if (error.value().data().has_value()
|
|
|
|
|
&& error.value().data().value().retry().value_or(false)) {
|
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();
|
|
|
|
|
QStringList error;
|
2019-07-18 15:09:43 +02:00
|
|
|
if (!result.isValid(&error)) { // continue on ill formed result
|
|
|
|
|
std::reverse(error.begin(), error.end());
|
2018-07-13 12:33:46 +02:00
|
|
|
log(tr("Initialize result is not valid: ") + error.join("->"));
|
2019-07-18 15:09:43 +02:00
|
|
|
}
|
2018-07-13 12:33:46 +02:00
|
|
|
|
|
|
|
|
m_serverCapabilities = result.capabilities().value_or(ServerCapabilities());
|
|
|
|
|
}
|
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())
|
|
|
|
|
.triggerCharacters()
|
|
|
|
|
.value_or(QList<QString>()));
|
|
|
|
|
}
|
|
|
|
|
if (auto functionHintAssistProvider = qobject_cast<FunctionHintAssistProvider *>(
|
|
|
|
|
m_clientProviders.completionAssistProvider)) {
|
|
|
|
|
functionHintAssistProvider->setTriggerCharacters(
|
|
|
|
|
m_serverCapabilities.signatureHelpProvider()
|
|
|
|
|
.value_or(ServerCapabilities::SignatureHelpOptions())
|
|
|
|
|
.triggerCharacters()
|
|
|
|
|
.value_or(QList<QString>()));
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " initialized";
|
|
|
|
|
m_state = Initialized;
|
|
|
|
|
sendContent(InitializeNotification());
|
2019-07-16 12:29:20 +02:00
|
|
|
if (m_dynamicCapabilities.isRegistered(DocumentSymbolsRequest::methodName)
|
|
|
|
|
.value_or(capabilities().documentSymbolProvider().value_or(false))) {
|
|
|
|
|
TextEditor::IOutlineWidgetFactory::updateOutline();
|
|
|
|
|
}
|
2019-09-10 08:03:58 +02:00
|
|
|
|
|
|
|
|
for (TextEditor::TextDocument *document : m_openedDocument.keys())
|
|
|
|
|
openDocument(document);
|
|
|
|
|
|
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
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_state == ShutdownRequested, return);
|
2019-01-31 08:46:23 +01:00
|
|
|
QTC_ASSERT(m_clientInterface, return);
|
2019-01-07 13:24:01 +01:00
|
|
|
optional<ShutdownRequest::Response::Error> errorValue = shutdownResponse.error();
|
2018-07-13 12:33:46 +02:00
|
|
|
if (errorValue.has_value()) {
|
2019-01-07 13:24:01 +01:00
|
|
|
ShutdownRequest::Response::Error error = errorValue.value();
|
2018-07-13 12:33:46 +02:00
|
|
|
qDebug() << error;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// directly send data otherwise the state check would fail;
|
2019-01-31 08:46:23 +01:00
|
|
|
m_clientInterface->sendMessage(ExitNotification().toBaseMessage());
|
2018-07-13 12:33:46 +02:00
|
|
|
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown";
|
|
|
|
|
m_state = Shutdown;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-31 12:15:43 +01:00
|
|
|
bool Client::sendWorkspceFolderChanges() const
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace LanguageClient
|