From 307f1d8e6eb24a88c2113b6b03b3133092ff81b7 Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 12 Jun 2019 12:55:06 +0200 Subject: [PATCH] LanguageClient: add support for proposed semantic highlight implements the current proposal for the semantic highlighting via the language server protocol. https://github.com/microsoft/vscode-languageserver-node/pull/367 Change-Id: I857d606fcf5c782e0ea8e18e5d098edd26286aed Reviewed-by: Nikolai Kosjar --- .../clientcapabilities.cpp | 35 ++-- .../clientcapabilities.h | 20 +++ src/libs/languageserverprotocol/jsonkeys.h | 5 + .../languagefeatures.cpp | 56 ++++++ .../languageserverprotocol/languagefeatures.h | 56 ++++++ .../servercapabilities.cpp | 43 ++++- .../servercapabilities.h | 17 ++ src/libs/utils/algorithm.h | 14 ++ src/plugins/languageclient/CMakeLists.txt | 1 + src/plugins/languageclient/client.cpp | 64 ++++++- src/plugins/languageclient/client.h | 5 + src/plugins/languageclient/languageclient.pro | 6 +- src/plugins/languageclient/languageclient.qbs | 2 + .../languageclient/languageclientmanager.cpp | 1 + .../semantichighlightsupport.cpp | 161 ++++++++++++++++++ .../languageclient/semantichighlightsupport.h | 46 +++++ .../texteditor/semantichighlighter.cpp | 2 + src/plugins/texteditor/semantichighlighter.h | 4 +- src/plugins/texteditor/texteditorconstants.h | 4 +- 19 files changed, 515 insertions(+), 27 deletions(-) create mode 100644 src/plugins/languageclient/semantichighlightsupport.cpp create mode 100644 src/plugins/languageclient/semantichighlightsupport.h diff --git a/src/libs/languageserverprotocol/clientcapabilities.cpp b/src/libs/languageserverprotocol/clientcapabilities.cpp index f73c4ee04ce..1f79ffae0d9 100644 --- a/src/libs/languageserverprotocol/clientcapabilities.cpp +++ b/src/libs/languageserverprotocol/clientcapabilities.cpp @@ -76,23 +76,24 @@ bool TextDocumentClientCapabilities::SynchronizationCapabilities::isValid(QStrin bool TextDocumentClientCapabilities::isValid(QStringList *error) const { return checkOptional(error, synchronizationKey) - && checkOptional(error, completionKey) - && checkOptional(error, hoverKey) - && checkOptional(error, signatureHelpKey) - && checkOptional(error, referencesKey) - && checkOptional(error, documentHighlightKey) - && checkOptional(error, documentSymbolKey) - && checkOptional(error, formattingKey) - && checkOptional(error, rangeFormattingKey) - && checkOptional(error, onTypeFormattingKey) - && checkOptional(error, definitionKey) - && checkOptional(error, typeDefinitionKey) - && checkOptional(error, implementationKey) - && checkOptional(error, codeActionKey) - && checkOptional(error, codeLensKey) - && checkOptional(error, documentLinkKey) - && checkOptional(error, colorProviderKey) - && checkOptional(error, renameKey); + && checkOptional(error, completionKey) + && checkOptional(error, hoverKey) + && checkOptional(error, signatureHelpKey) + && checkOptional(error, referencesKey) + && checkOptional(error, documentHighlightKey) + && checkOptional(error, documentSymbolKey) + && checkOptional(error, formattingKey) + && checkOptional(error, rangeFormattingKey) + && checkOptional(error, onTypeFormattingKey) + && checkOptional(error, definitionKey) + && checkOptional(error, typeDefinitionKey) + && checkOptional(error, implementationKey) + && checkOptional(error, codeActionKey) + && checkOptional(error, codeLensKey) + && checkOptional(error, documentLinkKey) + && checkOptional(error, colorProviderKey) + && checkOptional(error, renameKey) + && checkOptional(error, semanticHighlightingCapabilitiesKey); } bool SymbolCapabilities::isValid(QStringList *error) const diff --git a/src/libs/languageserverprotocol/clientcapabilities.h b/src/libs/languageserverprotocol/clientcapabilities.h index 3664007dd19..ec1bdd25b55 100644 --- a/src/libs/languageserverprotocol/clientcapabilities.h +++ b/src/libs/languageserverprotocol/clientcapabilities.h @@ -120,6 +120,26 @@ public: { insert(synchronizationKey, synchronization); } void clearSynchronization() { remove(synchronizationKey); } + class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + bool semanticHighlighting() const { return typedValue(semanticHighlightingKey); } + void setSemanticHighlighting(bool semanticHighlighting) + { insert(semanticHighlightingKey, semanticHighlighting); } + + bool isValid(QStringList *error) const override + { return check(error, semanticHighlightingKey); } + }; + + Utils::optional semanticHighlightingCapabilities() const + { return optionalValue(semanticHighlightingCapabilitiesKey); } + void setSemanticHighlightingCapabilities( + const SemanticHighlightingCapabilities &semanticHighlightingCapabilities) + { insert(semanticHighlightingCapabilitiesKey, semanticHighlightingCapabilities); } + void clearSemanticHighlightingCapabilities() { remove(semanticHighlightingCapabilitiesKey); } + class LANGUAGESERVERPROTOCOL_EXPORT CompletionCapabilities : public DynamicRegistrationCapabilities { public: diff --git a/src/libs/languageserverprotocol/jsonkeys.h b/src/libs/languageserverprotocol/jsonkeys.h index e3673085ebb..4c4ee054ab4 100644 --- a/src/libs/languageserverprotocol/jsonkeys.h +++ b/src/libs/languageserverprotocol/jsonkeys.h @@ -130,6 +130,7 @@ constexpr char labelKey[] = "label"; constexpr char languageIdKey[] = "languageId"; constexpr char languageKey[] = "language"; constexpr char lineKey[] = "line"; +constexpr char linesKey[] = "lines"; constexpr char locationKey[] = "location"; constexpr char messageKey[] = "message"; constexpr char methodKey[] = "method"; @@ -166,8 +167,11 @@ constexpr char rootUriKey[] = "rootUri"; constexpr char saveKey[] = "save"; constexpr char schemeKey[] = "scheme"; constexpr char scopeUriKey[] = "scopeUri"; +constexpr char scopesKey[] = "scopes"; constexpr char sectionKey[] = "section"; constexpr char selectionRangeKey[] = "selectionRange"; +constexpr char semanticHighlightingKey[] = "semanticHighlighting"; +constexpr char semanticHighlightingCapabilitiesKey[] = "semanticHighlightingCapabilities"; constexpr char settingsKey[] = "settings"; constexpr char severityKey[] = "severity"; constexpr char signatureHelpKey[] = "signatureHelp"; @@ -190,6 +194,7 @@ constexpr char textDocumentSyncKey[] = "textDocumentSync"; constexpr char textEditKey[] = "textEdit"; constexpr char textKey[] = "text"; constexpr char titleKey[] = "title"; +constexpr char tokensKey[] = "tokens"; constexpr char traceKey[] = "trace"; constexpr char triggerCharacterKey[] = "triggerCharacter"; constexpr char triggerCharactersKey[] = "triggerCharacters"; diff --git a/src/libs/languageserverprotocol/languagefeatures.cpp b/src/libs/languageserverprotocol/languagefeatures.cpp index cfb4cc6fabd..f66b6b7e164 100644 --- a/src/libs/languageserverprotocol/languagefeatures.cpp +++ b/src/libs/languageserverprotocol/languagefeatures.cpp @@ -48,6 +48,7 @@ constexpr const char DocumentRangeFormattingRequest::methodName[]; constexpr const char DocumentOnTypeFormattingRequest::methodName[]; constexpr const char RenameRequest::methodName[]; constexpr const char SignatureHelpRequest::methodName[]; +constexpr const char SemanticHighlightNotification::methodName[]; HoverContent LanguageServerProtocol::Hover::content() const { @@ -441,4 +442,59 @@ bool CodeAction::isValid(QStringList *error) const && checkOptional(error, commandKey); } +Utils::optional> SemanticHighlightingInformation::tokens() const +{ + QList resultTokens; + + const QByteArray tokensByteArray = QByteArray::fromBase64( + typedValue(tokensKey).toLocal8Bit()); + constexpr int tokensByteSize = 8; + int index = 0; + while (index + tokensByteSize <= tokensByteArray.size()) { + resultTokens << SemanticHighlightToken(tokensByteArray.mid(index, tokensByteSize)); + index += tokensByteSize; + } + return Utils::make_optional(resultTokens); +} + +void SemanticHighlightingInformation::setTokens(const QList &tokens) +{ + QByteArray byteArray; + byteArray.reserve(8 * tokens.size()); + for (const SemanticHighlightToken &token : tokens) + token.appendToByteArray(byteArray); + insert(tokensKey, QString::fromLocal8Bit(byteArray.toBase64())); +} + +SemanticHighlightToken::SemanticHighlightToken(const QByteArray &token) +{ + QTC_ASSERT(token.size() == 8, return ); + character = ( quint32(token.at(0)) << 24 + | quint32(token.at(1)) << 16 + | quint32(token.at(2)) << 8 + | quint32(token.at(3))); + + length = quint16(token.at(4) << 8 | token.at(5)); + + scope = quint16(token.at(6) << 8 | token.at(7)); +} + +void SemanticHighlightToken::appendToByteArray(QByteArray &byteArray) const +{ + byteArray.append(char((character & 0xff000000) >> 24)); + byteArray.append(char((character & 0x00ff0000) >> 16)); + byteArray.append(char((character & 0x0000ff00) >> 8)); + byteArray.append(char((character & 0x000000ff))); + byteArray.append(char((length & 0xff00) >> 8)); + byteArray.append(char((length & 0x00ff))); + byteArray.append(char((scope & 0xff00) >> 8)); + byteArray.append(char((scope & 0x00ff))); +} + +bool SemanticHighlightingParams::isValid(QStringList *error) const +{ + return check(error, textDocumentKey) + && checkArray(error, linesKey); +} + } // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/languagefeatures.h b/src/libs/languageserverprotocol/languagefeatures.h index ddd732718a1..bb98d7f8bd8 100644 --- a/src/libs/languageserverprotocol/languagefeatures.h +++ b/src/libs/languageserverprotocol/languagefeatures.h @@ -806,4 +806,60 @@ public: constexpr static const char methodName[] = "textDocument/rename"; }; +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightToken +{ +public: + // Just accepts token with 8 bytes + SemanticHighlightToken(const QByteArray &token); + SemanticHighlightToken() = default; + + void appendToByteArray(QByteArray &byteArray) const; + + quint32 character = 0; + quint16 length = 0; + quint16 scope = 0; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingInformation : public JsonObject +{ +public: + using JsonObject::JsonObject; + + int line() const { return typedValue(lineKey); } + void setLine(int line) { insert(lineKey, line); } + + Utils::optional> tokens() const; + void setTokens(const QList &tokens); + void clearTokens() { remove(tokensKey); } + + bool isValid(QStringList *error) const override + { return check(error, lineKey) && checkOptional(error, tokensKey); } +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingParams : public JsonObject +{ +public: + using JsonObject::JsonObject; + + VersionedTextDocumentIdentifier textDocument() const + { return typedValue(textDocumentKey); } + void setTextDocument(const VersionedTextDocumentIdentifier &textDocument) + { insert(textDocumentKey, textDocument); } + + QList lines() const + { return array(linesKey); } + void setLines(const QList &lines) + { insertArray(linesKey, lines); } + + bool isValid(QStringList *error) const override; +}; + +class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightNotification + : public Notification +{ +public: + using Notification::Notification; + constexpr static const char methodName[] = "textDocument/semanticHighlighting"; +}; + } // namespace LanguageClient diff --git a/src/libs/languageserverprotocol/servercapabilities.cpp b/src/libs/languageserverprotocol/servercapabilities.cpp index 6ebf4f431fa..f9dce15b35c 100644 --- a/src/libs/languageserverprotocol/servercapabilities.cpp +++ b/src/libs/languageserverprotocol/servercapabilities.cpp @@ -132,7 +132,8 @@ bool ServerCapabilities::isValid(QStringList *error) const && checkOptional(error, documentLinkProviderKey) && checkOptional(error, colorProviderKey) && checkOptional(error, executeCommandProviderKey) - && checkOptional(error, workspaceKey); + && checkOptional(error, workspaceKey) + && checkOptional(error, semanticHighlightingKey); } Utils::optional > @@ -181,4 +182,44 @@ bool TextDocumentSyncOptions::isValid(QStringList *error) const && checkOptional(error, saveKey); } +Utils::optional>> ServerCapabilities::SemanticHighlightingServerCapabilities::scopes() const +{ + QList> scopes; + if (!contains(scopesKey)) + return Utils::nullopt; + for (const QJsonValue jsonScopeValue : value(scopesKey).toArray()) { + if (!jsonScopeValue.isArray()) + return {}; + QList scope; + for (const QJsonValue value : jsonScopeValue.toArray()) { + if (!value.isString()) + return {}; + scope.append(value.toString()); + } + scopes.append(scope); + } + return Utils::make_optional(scopes); +} + +void ServerCapabilities::SemanticHighlightingServerCapabilities::setScopes( + const QList> &scopes) +{ + QJsonArray jsonScopes; + for (const QList &scope : scopes) { + QJsonArray jsonScope; + for (const QString &value : scope) + jsonScope.append(value); + jsonScopes.append(jsonScope); + } + insert(scopesKey, jsonScopes); +} + +bool ServerCapabilities::SemanticHighlightingServerCapabilities::isValid(QStringList *) const +{ + return contains(scopesKey) && value(scopesKey).isArray() + && Utils::allOf(value(scopesKey).toArray(), [](const QJsonValue &array) { + return array.isArray() && Utils::allOf(array.toArray(), &QJsonValue::isString); + }); +} + } // namespace LanguageServerProtocol diff --git a/src/libs/languageserverprotocol/servercapabilities.h b/src/libs/languageserverprotocol/servercapabilities.h index 71d3e4a4965..6345178c570 100644 --- a/src/libs/languageserverprotocol/servercapabilities.h +++ b/src/libs/languageserverprotocol/servercapabilities.h @@ -223,6 +223,17 @@ public: void clearId() { remove(idKey); } }; + class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingServerCapabilities : public JsonObject + { + public: + using JsonObject::JsonObject; + + Utils::optional>> scopes() const; + void setScopes(const QList> &scopes); + + bool isValid(QStringList *) const override; + }; + // Defines how text documents are synced. Is either a detailed structure defining each // notification or for backwards compatibility the TextDocumentSyncKind number. using TextDocumentSync = Utils::variant; @@ -411,6 +422,12 @@ public: void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); } void clearExperimental() { remove(experimentalKey); } + Utils::optional semanticHighlighting() const + { return optionalValue(semanticHighlightingKey); } + void setSemanticHighlighting(const SemanticHighlightingServerCapabilities &semanticHighlighting) + { insert(semanticHighlightingKey, semanticHighlighting); } + void clearSemanticHighlighting() { remove(semanticHighlightingKey); } + bool isValid(QStringList *error) const override; }; diff --git a/src/libs/utils/algorithm.h b/src/libs/utils/algorithm.h index f1159e2d1ae..4b7231cca04 100644 --- a/src/libs/utils/algorithm.h +++ b/src/libs/utils/algorithm.h @@ -407,6 +407,20 @@ bool allOf(const T &container, F predicate) return std::all_of(std::begin(container), std::end(container), predicate); } +// allOf taking a member function pointer +template +bool allOf(const T &container, R (S::*predicate)() const) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(predicate)); +} + +// allOf taking a member pointer +template +bool allOf(const T &container, R S::*member) +{ + return std::all_of(std::begin(container), std::end(container), std::mem_fn(member)); +} + ////////////////// // erase ///////////////// diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt index 36040c1f5ce..a1366444e54 100644 --- a/src/plugins/languageclient/CMakeLists.txt +++ b/src/plugins/languageclient/CMakeLists.txt @@ -18,4 +18,5 @@ add_qtc_plugin(LanguageClient languageclientutils.cpp languageclientutils.h languageclient_global.h locatorfilter.cpp locatorfilter.h + semantichighlightsupport.cpp semantichighlightsupport.h ) diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 3acea150800..a17bb47a131 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -28,6 +28,7 @@ #include "languageclientinterface.h" #include "languageclientmanager.h" #include "languageclientutils.h" +#include "semantichighlightsupport.h" #include #include @@ -39,9 +40,10 @@ #include #include #include -#include +#include #include #include +#include #include #include #include @@ -105,6 +107,10 @@ Client::Client(BaseClientInterface *clientInterface) connect(clientInterface, &BaseClientInterface::messageReceived, this, &Client::handleMessage); connect(clientInterface, &BaseClientInterface::error, this, &Client::setError); connect(clientInterface, &BaseClientInterface::finished, this, &Client::finished); + connect(TextEditor::TextEditorSettings::instance(), + &TextEditor::TextEditorSettings::fontSettingsChanged, + this, + &Client::rehighlight); } static void updateEditorToolBar(QList files) @@ -137,6 +143,12 @@ Client::~Client() } for (const DocumentUri &uri : m_diagnostics.keys()) removeDiagnostics(uri); + for (const DocumentUri &uri : m_highlights.keys()) { + if (TextDocument *doc = TextDocument::textDocumentForFileName(uri.toFileName())) { + if (TextEditor::SyntaxHighlighter *highlighter = doc->syntaxHighlighter()) + highlighter->clearAllExtraFormats(); + } + } updateEditorToolBar(m_openedDocument.keys()); } @@ -175,6 +187,10 @@ static ClientCapabilities generateClientCapabilities() symbolCapabilities.setSymbolKind(symbolKindCapabilities); documentCapabilities.setDocumentSymbol(symbolCapabilities); + TextDocumentClientCapabilities::SemanticHighlightingCapabilities semanticHighlight; + semanticHighlight.setSemanticHighlighting(true); + documentCapabilities.setSemanticHighlightingCapabilities(semanticHighlight); + TextDocumentClientCapabilities::CompletionCapabilities completionCapabilities; completionCapabilities.setDynamicRegistration(true); TextDocumentClientCapabilities::CompletionCapabilities::CompletionItemKindCapabilities @@ -366,7 +382,9 @@ void Client::cancelRequest(const MessageId &id) void Client::closeDocument(const DidCloseTextDocumentParams ¶ms) { - sendContent(params.textDocument().uri(), DidCloseTextDocumentNotification(params)); + const DocumentUri &uri = params.textDocument().uri(); + m_highlights[uri].clear(); + sendContent(uri, DidCloseTextDocumentNotification(params)); } bool Client::documentOpen(const Core::IDocument *document) const @@ -455,8 +473,9 @@ void Client::documentContentsChanged(TextEditor::TextDocument *document, } auto textDocument = qobject_cast(document); + const auto uri = DocumentUri::fromFileName(document->filePath()); + m_highlights[uri].clear(); if (syncKind != TextDocumentSyncKind::None) { - const auto uri = DocumentUri::fromFileName(document->filePath()); VersionedTextDocumentIdentifier docId(uri); docId.setVersion(textDocument ? textDocument->document()->revision() : 0); DidChangeTextDocumentParams params; @@ -538,8 +557,10 @@ TextEditor::HighlightingResult createHighlightingResult(const SymbolInformation if (!info.isValid(nullptr)) return {}; const Position &start = info.location().range().start(); - return TextEditor::HighlightingResult(start.line() + 1, start.character() + 1, - info.name().length(), info.kind()); + return TextEditor::HighlightingResult(unsigned(start.line() + 1), + unsigned(start.character() + 1), + unsigned(info.name().length()), + info.kind()); } void Client::requestDocumentSymbols(TextEditor::TextDocument *document) @@ -1011,6 +1032,11 @@ void Client::handleMethod(const QString &method, MessageId id, const IContent *c paramsValid = params.isValid(&error); if (paramsValid) log(params, Core::MessageManager::Flash); + } else if (method == SemanticHighlightNotification::methodName) { + auto params = dynamic_cast(content)->params().value_or(SemanticHighlightingParams()); + paramsValid = params.isValid(&error); + if (paramsValid) + handleSemanticHighlight(params); } else if (method == ShowMessageNotification::methodName) { auto params = dynamic_cast(content)->params().value_or(ShowMessageParams()); paramsValid = params.isValid(&error); @@ -1093,6 +1119,34 @@ void Client::handleDiagnostics(const PublishDiagnosticsParams ¶ms) requestCodeActions(uri, diagnostics); } +void Client::handleSemanticHighlight(const SemanticHighlightingParams ¶ms) +{ + const DocumentUri &uri = params.textDocument().uri(); + m_highlights[uri].clear(); + const LanguageClientValue &version = params.textDocument().version(); + TextEditor::TextDocument *doc = TextEditor::TextDocument::textDocumentForFileName( + uri.toFileName()); + + if (!doc || (!version.isNull() && doc->document()->revision() != version.value())) + return; + + 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) { + if (TextDocument *doc = TextDocument::textDocumentForFileName(it.key().toFileName())) + SemanticHighligtingSupport::applyHighlight(doc, it.value(), capabilities()); + } +} + void Client::intializeCallback(const InitializeRequest::Response &initResponse) { QTC_ASSERT(m_state == InitializeRequested, return); diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h index 9746607e627..eced6780bf1 100644 --- a/src/plugins/languageclient/client.h +++ b/src/plugins/languageclient/client.h @@ -45,6 +45,8 @@ #include #include +#include + #include #include #include @@ -158,6 +160,7 @@ public: const BaseClientInterface *clientInterface() const; DocumentSymbolCache *documentSymbolCache(); HoverHandler *hoverHandler(); + void rehighlight(); signals: void initialized(LanguageServerProtocol::ServerCapabilities capabilities); @@ -174,6 +177,7 @@ private: const LanguageServerProtocol::IContent *content); void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms); + void handleSemanticHighlight(const LanguageServerProtocol::SemanticHighlightingParams ¶ms); void intializeCallback(const LanguageServerProtocol::InitializeRequest::Response &initResponse); void shutDownCallback(const LanguageServerProtocol::ShutdownRequest::Response &shutdownResponse); @@ -210,6 +214,7 @@ private: QMap> m_diagnostics; DocumentSymbolCache m_documentSymbolCache; HoverHandler m_hoverHandler; + QHash m_highlights; const ProjectExplorer::Project *m_project = nullptr; }; diff --git a/src/plugins/languageclient/languageclient.pro b/src/plugins/languageclient/languageclient.pro index b2d39acf16c..755c0bcc29c 100644 --- a/src/plugins/languageclient/languageclient.pro +++ b/src/plugins/languageclient/languageclient.pro @@ -17,7 +17,8 @@ HEADERS += \ languageclientquickfix.h \ languageclientsettings.h \ languageclientutils.h \ - locatorfilter.h + locatorfilter.h \ + semantichighlightsupport.h SOURCES += \ @@ -34,7 +35,8 @@ SOURCES += \ languageclientquickfix.cpp \ languageclientsettings.cpp \ languageclientutils.cpp \ - locatorfilter.cpp + locatorfilter.cpp \ + semantichighlightsupport.cpp RESOURCES += \ languageclient.qrc diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 13ca2105703..5ff9c340755 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -44,5 +44,7 @@ QtcPlugin { "languageclientutils.h", "locatorfilter.cpp", "locatorfilter.h", + "semantichighlightsupport.cpp", + "semantichighlightsupport.h", ] } diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 4c5ec02a594..74c33473699 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -57,6 +57,7 @@ LanguageClientManager::LanguageClientManager(QObject *parent) using namespace Core; using namespace ProjectExplorer; JsonRpcMessageHandler::registerMessageProvider(); + JsonRpcMessageHandler::registerMessageProvider(); JsonRpcMessageHandler::registerMessageProvider(); JsonRpcMessageHandler::registerMessageProvider(); JsonRpcMessageHandler::registerMessageProvider(); diff --git a/src/plugins/languageclient/semantichighlightsupport.cpp b/src/plugins/languageclient/semantichighlightsupport.cpp new file mode 100644 index 00000000000..59b415455f7 --- /dev/null +++ b/src/plugins/languageclient/semantichighlightsupport.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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. +** +****************************************************************************/ + +#include "semantichighlightsupport.h" + +#include + +using namespace LanguageServerProtocol; + +namespace LanguageClient { +namespace SemanticHighligtingSupport { + +static Q_LOGGING_CATEGORY(LOGLSPHIGHLIGHT, "qtc.languageclient.highlight", QtWarningMsg); + +static const QList> highlightScopes(const ServerCapabilities &capabilities) +{ + return capabilities.semanticHighlighting() + .value_or(ServerCapabilities::SemanticHighlightingServerCapabilities()) + .scopes().value_or(QList>()); +} + +static Utils::optional styleForScopes(const QList &scopes) +{ + // missing "Minimal Scope Coverage" scopes + + // entity.other.inherited-class + // entity.name.section + // entity.name.tag + // entity.other.attribute-name + // variable.language + // variable.parameter + // variable.function + // constant.numeric + // constant.language + // constant.character.escape + // support + // storage.modifier + // keyword.control + // keyword.operator + // keyword.declaration + // invalid + // invalid.deprecated + + static const QMap styleForScopes = { + {"entity.name", TextEditor::C_TYPE}, + {"entity.name.function", TextEditor::C_FUNCTION}, + {"entity.name.label", TextEditor::C_LABEL}, + {"keyword", TextEditor::C_KEYWORD}, + {"storage.type", TextEditor::C_KEYWORD}, + {"constant.numeric", TextEditor::C_NUMBER}, + {"string", TextEditor::C_STRING}, + {"comment", TextEditor::C_COMMENT}, + {"comment.block.documentation", TextEditor::C_DOXYGEN_COMMENT}, + {"variable.function", TextEditor::C_FUNCTION}, + {"variable.other", TextEditor::C_LOCAL}, + {"variable.other.member", TextEditor::C_FIELD}, + {"variable.parameter", TextEditor::C_LOCAL}, + }; + + for (QString scope : scopes) { + while (!scope.isEmpty()) { + auto style = styleForScopes.find(scope); + if (style != styleForScopes.end()) + return style.value(); + const int index = scope.lastIndexOf('.'); + if (index <= 0) + break; + scope = scope.left(index); + } + } + return Utils::nullopt; +} + +static QHash scopesToFormatHash(QList> scopes, + const TextEditor::FontSettings &fontSettings) +{ + QHash scopesToFormat; + for (int i = 0; i < scopes.size(); ++i) { + if (Utils::optional style = styleForScopes(scopes[i])) + scopesToFormat[i] = fontSettings.toTextCharFormat(style.value()); + } + return scopesToFormat; +} + +TextEditor::HighlightingResult tokenToHighlightingResult(int line, + const SemanticHighlightToken &token) +{ + return TextEditor::HighlightingResult(unsigned(line) + 1, + unsigned(token.character) + 1, + token.length, + int(token.scope)); +} + +TextEditor::HighlightingResults generateResults(const QList &lines) +{ + TextEditor::HighlightingResults results; + + for (const SemanticHighlightingInformation &info : lines) { + const int line = info.line(); + for (const SemanticHighlightToken &token : + info.tokens().value_or(QList())) { + results << tokenToHighlightingResult(line, token); + } + } + + return results; +} + +void applyHighlight(TextEditor::TextDocument *doc, + const TextEditor::HighlightingResults &results, + const ServerCapabilities &capabilities) +{ + if (!doc->syntaxHighlighter()) + return; + if (LOGLSPHIGHLIGHT().isDebugEnabled()) { + auto scopes = highlightScopes(capabilities); + qCDebug(LOGLSPHIGHLIGHT) << "semantic highlight for" << doc->filePath(); + for (auto result : results) { + auto b = doc->document()->findBlockByNumber(int(result.line - 1)); + const QString &text = b.text().mid(int(result.column - 1), int(result.length)); + auto resultScupes = scopes[result.kind]; + auto style = styleForScopes(resultScupes).value_or(TextEditor::C_TEXT); + qCDebug(LOGLSPHIGHLIGHT) << result.line - 1 << '\t' + << result.column - 1 << '\t' + << result.length << '\t' + << TextEditor::Constants::nameForStyle(style) << '\t' + << text + << resultScupes; + } + } + + TextEditor::SemanticHighlighter::setExtraAdditionalFormats( + doc->syntaxHighlighter(), + results, + scopesToFormatHash(highlightScopes(capabilities), doc->fontSettings())); +} + +} // namespace SemanticHighligtingSupport +} // namespace LanguageClient diff --git a/src/plugins/languageclient/semantichighlightsupport.h b/src/plugins/languageclient/semantichighlightsupport.h new file mode 100644 index 00000000000..b9d4fe1555d --- /dev/null +++ b/src/plugins/languageclient/semantichighlightsupport.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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. +** +****************************************************************************/ + +#pragma once + +#include "languageclient_global.h" + +#include +#include +#include +#include + +namespace LanguageClient { +namespace SemanticHighligtingSupport { + +TextEditor::HighlightingResults generateResults( + const QList &lines); + +void applyHighlight(TextEditor::TextDocument *doc, + const TextEditor::HighlightingResults &results, + const LanguageServerProtocol::ServerCapabilities &capabilities); + +} // namespace SemanticHighligtingSupport +} // namespace LanguageClient diff --git a/src/plugins/texteditor/semantichighlighter.cpp b/src/plugins/texteditor/semantichighlighter.cpp index be0e66065ae..858f8bb8cd6 100644 --- a/src/plugins/texteditor/semantichighlighter.cpp +++ b/src/plugins/texteditor/semantichighlighter.cpp @@ -121,6 +121,8 @@ void SemanticHighlighter::setExtraAdditionalFormats(SyntaxHighlighter *highlight const QList &results, const QHash &kindToFormat) { + if (!highlighter) + return; highlighter->clearAllExtraFormats(); QTextDocument *doc = highlighter->document(); diff --git a/src/plugins/texteditor/semantichighlighter.h b/src/plugins/texteditor/semantichighlighter.h index d39ba7b80d0..f924e4abdb3 100644 --- a/src/plugins/texteditor/semantichighlighter.h +++ b/src/plugins/texteditor/semantichighlighter.h @@ -71,6 +71,8 @@ public: } }; +using HighlightingResults = QList; + namespace SemanticHighlighter { // Applies the future results [from, to) and applies the extra formats @@ -92,7 +94,7 @@ void TEXTEDITOR_EXPORT incrementalApplyExtraAdditionalFormats( // incrementalApplyExtraAdditionalFormats the results do not have to be ordered by line. void TEXTEDITOR_EXPORT setExtraAdditionalFormats( SyntaxHighlighter *highlighter, - const QList &results, + const HighlightingResults &results, const QHash &kindToFormat); // Cleans the extra additional formats after the last result of the Future diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 78c9c1cb714..b3905b51922 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -25,6 +25,8 @@ #pragma once +#include "texteditor_global.h" + #include namespace TextEditor { @@ -204,7 +206,7 @@ const char JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.JumpToFileUnd const char SCROLL_BAR_SEARCH_RESULT[] = "TextEditor.ScrollBarSearchResult"; const char SCROLL_BAR_CURRENT_LINE[] = "TextEditor.ScrollBarCurrentLine"; -const char *nameForStyle(TextStyle style); +const TEXTEDITOR_EXPORT char *nameForStyle(TextStyle style); TextStyle styleFromName(const char *name); const char TEXT_EDITOR_SETTINGS_CATEGORY[] = "C.TextEditor";