forked from qt-creator/qt-creator
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 <nikolai.kosjar@qt.io>
This commit is contained in:
@@ -92,7 +92,8 @@ bool TextDocumentClientCapabilities::isValid(QStringList *error) const
|
||||
&& checkOptional<DynamicRegistrationCapabilities>(error, codeLensKey)
|
||||
&& checkOptional<DynamicRegistrationCapabilities>(error, documentLinkKey)
|
||||
&& checkOptional<DynamicRegistrationCapabilities>(error, colorProviderKey)
|
||||
&& checkOptional<DynamicRegistrationCapabilities>(error, renameKey);
|
||||
&& checkOptional<DynamicRegistrationCapabilities>(error, renameKey)
|
||||
&& checkOptional<SemanticHighlightingCapabilities>(error, semanticHighlightingCapabilitiesKey);
|
||||
}
|
||||
|
||||
bool SymbolCapabilities::isValid(QStringList *error) const
|
||||
|
@@ -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<bool>(semanticHighlightingKey); }
|
||||
void setSemanticHighlighting(bool semanticHighlighting)
|
||||
{ insert(semanticHighlightingKey, semanticHighlighting); }
|
||||
|
||||
bool isValid(QStringList *error) const override
|
||||
{ return check<bool>(error, semanticHighlightingKey); }
|
||||
};
|
||||
|
||||
Utils::optional<SemanticHighlightingCapabilities> semanticHighlightingCapabilities() const
|
||||
{ return optionalValue<SemanticHighlightingCapabilities>(semanticHighlightingCapabilitiesKey); }
|
||||
void setSemanticHighlightingCapabilities(
|
||||
const SemanticHighlightingCapabilities &semanticHighlightingCapabilities)
|
||||
{ insert(semanticHighlightingCapabilitiesKey, semanticHighlightingCapabilities); }
|
||||
void clearSemanticHighlightingCapabilities() { remove(semanticHighlightingCapabilitiesKey); }
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT CompletionCapabilities : public DynamicRegistrationCapabilities
|
||||
{
|
||||
public:
|
||||
|
@@ -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";
|
||||
|
@@ -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<Command>(error, commandKey);
|
||||
}
|
||||
|
||||
Utils::optional<QList<SemanticHighlightToken>> SemanticHighlightingInformation::tokens() const
|
||||
{
|
||||
QList<SemanticHighlightToken> resultTokens;
|
||||
|
||||
const QByteArray tokensByteArray = QByteArray::fromBase64(
|
||||
typedValue<QString>(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<SemanticHighlightToken> &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<VersionedTextDocumentIdentifier>(error, textDocumentKey)
|
||||
&& checkArray<SemanticHighlightingInformation>(error, linesKey);
|
||||
}
|
||||
|
||||
} // namespace LanguageServerProtocol
|
||||
|
@@ -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<int>(lineKey); }
|
||||
void setLine(int line) { insert(lineKey, line); }
|
||||
|
||||
Utils::optional<QList<SemanticHighlightToken>> tokens() const;
|
||||
void setTokens(const QList<SemanticHighlightToken> &tokens);
|
||||
void clearTokens() { remove(tokensKey); }
|
||||
|
||||
bool isValid(QStringList *error) const override
|
||||
{ return check<int>(error, lineKey) && checkOptional<QString>(error, tokensKey); }
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingParams : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
|
||||
VersionedTextDocumentIdentifier textDocument() const
|
||||
{ return typedValue<VersionedTextDocumentIdentifier>(textDocumentKey); }
|
||||
void setTextDocument(const VersionedTextDocumentIdentifier &textDocument)
|
||||
{ insert(textDocumentKey, textDocument); }
|
||||
|
||||
QList<SemanticHighlightingInformation> lines() const
|
||||
{ return array<SemanticHighlightingInformation>(linesKey); }
|
||||
void setLines(const QList<SemanticHighlightingInformation> &lines)
|
||||
{ insertArray(linesKey, lines); }
|
||||
|
||||
bool isValid(QStringList *error) const override;
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightNotification
|
||||
: public Notification<SemanticHighlightingParams>
|
||||
{
|
||||
public:
|
||||
using Notification::Notification;
|
||||
constexpr static const char methodName[] = "textDocument/semanticHighlighting";
|
||||
};
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -132,7 +132,8 @@ bool ServerCapabilities::isValid(QStringList *error) const
|
||||
&& checkOptional<DocumentLinkOptions>(error, documentLinkProviderKey)
|
||||
&& checkOptional<TextDocumentRegistrationOptions>(error, colorProviderKey)
|
||||
&& checkOptional<ExecuteCommandOptions>(error, executeCommandProviderKey)
|
||||
&& checkOptional<WorkspaceServerCapabilities>(error, workspaceKey);
|
||||
&& checkOptional<WorkspaceServerCapabilities>(error, workspaceKey)
|
||||
&& checkOptional<SemanticHighlightingServerCapabilities>(error, semanticHighlightingKey);
|
||||
}
|
||||
|
||||
Utils::optional<Utils::variant<QString, bool> >
|
||||
@@ -181,4 +182,44 @@ bool TextDocumentSyncOptions::isValid(QStringList *error) const
|
||||
&& checkOptional<SaveOptions>(error, saveKey);
|
||||
}
|
||||
|
||||
Utils::optional<QList<QList<QString>>> ServerCapabilities::SemanticHighlightingServerCapabilities::scopes() const
|
||||
{
|
||||
QList<QList<QString>> scopes;
|
||||
if (!contains(scopesKey))
|
||||
return Utils::nullopt;
|
||||
for (const QJsonValue jsonScopeValue : value(scopesKey).toArray()) {
|
||||
if (!jsonScopeValue.isArray())
|
||||
return {};
|
||||
QList<QString> 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<QList<QString>> &scopes)
|
||||
{
|
||||
QJsonArray jsonScopes;
|
||||
for (const QList<QString> &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
|
||||
|
@@ -223,6 +223,17 @@ public:
|
||||
void clearId() { remove(idKey); }
|
||||
};
|
||||
|
||||
class LANGUAGESERVERPROTOCOL_EXPORT SemanticHighlightingServerCapabilities : public JsonObject
|
||||
{
|
||||
public:
|
||||
using JsonObject::JsonObject;
|
||||
|
||||
Utils::optional<QList<QList<QString>>> scopes() const;
|
||||
void setScopes(const QList<QList<QString>> &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<TextDocumentSyncOptions, int>;
|
||||
@@ -411,6 +422,12 @@ public:
|
||||
void setExperimental(const JsonObject &experimental) { insert(experimentalKey, experimental); }
|
||||
void clearExperimental() { remove(experimentalKey); }
|
||||
|
||||
Utils::optional<SemanticHighlightingServerCapabilities> semanticHighlighting() const
|
||||
{ return optionalValue<SemanticHighlightingServerCapabilities>(semanticHighlightingKey); }
|
||||
void setSemanticHighlighting(const SemanticHighlightingServerCapabilities &semanticHighlighting)
|
||||
{ insert(semanticHighlightingKey, semanticHighlighting); }
|
||||
void clearSemanticHighlighting() { remove(semanticHighlightingKey); }
|
||||
|
||||
bool isValid(QStringList *error) const override;
|
||||
};
|
||||
|
||||
|
@@ -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<typename T, typename R, typename S>
|
||||
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<typename T, typename R, typename S>
|
||||
bool allOf(const T &container, R S::*member)
|
||||
{
|
||||
return std::all_of(std::begin(container), std::end(container), std::mem_fn(member));
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// erase
|
||||
/////////////////
|
||||
|
@@ -18,4 +18,5 @@ add_qtc_plugin(LanguageClient
|
||||
languageclientutils.cpp languageclientutils.h
|
||||
languageclient_global.h
|
||||
locatorfilter.cpp locatorfilter.h
|
||||
semantichighlightsupport.cpp semantichighlightsupport.h
|
||||
)
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "languageclientinterface.h"
|
||||
#include "languageclientmanager.h"
|
||||
#include "languageclientutils.h"
|
||||
#include "semantichighlightsupport.h"
|
||||
|
||||
#include <coreplugin/icore.h>
|
||||
#include <coreplugin/idocument.h>
|
||||
@@ -39,9 +40,10 @@
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/session.h>
|
||||
#include <texteditor/codeassist/documentcontentcompletion.h>
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <texteditor/syntaxhighlighter.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <texteditor/texteditorsettings.h>
|
||||
#include <texteditor/textmark.h>
|
||||
#include <utils/mimetypes/mimedatabase.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
@@ -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<Utils::FilePath> 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<TextEditor::TextDocument *>(document);
|
||||
|
||||
if (syncKind != TextDocumentSyncKind::None) {
|
||||
const auto uri = DocumentUri::fromFileName(document->filePath());
|
||||
m_highlights[uri].clear();
|
||||
if (syncKind != TextDocumentSyncKind::None) {
|
||||
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<const SemanticHighlightNotification *>(content)->params().value_or(SemanticHighlightingParams());
|
||||
paramsValid = params.isValid(&error);
|
||||
if (paramsValid)
|
||||
handleSemanticHighlight(params);
|
||||
} else if (method == ShowMessageNotification::methodName) {
|
||||
auto params = dynamic_cast<const ShowMessageNotification *>(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<int> &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);
|
||||
|
@@ -45,6 +45,8 @@
|
||||
#include <languageserverprotocol/shutdownmessages.h>
|
||||
#include <languageserverprotocol/textsynchronization.h>
|
||||
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QHash>
|
||||
#include <QProcess>
|
||||
@@ -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<LanguageServerProtocol::DocumentUri, QList<TextMark *>> m_diagnostics;
|
||||
DocumentSymbolCache m_documentSymbolCache;
|
||||
HoverHandler m_hoverHandler;
|
||||
QHash<LanguageServerProtocol::DocumentUri, TextEditor::HighlightingResults> m_highlights;
|
||||
const ProjectExplorer::Project *m_project = nullptr;
|
||||
};
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -44,5 +44,7 @@ QtcPlugin {
|
||||
"languageclientutils.h",
|
||||
"locatorfilter.cpp",
|
||||
"locatorfilter.h",
|
||||
"semantichighlightsupport.cpp",
|
||||
"semantichighlightsupport.h",
|
||||
]
|
||||
}
|
||||
|
@@ -57,6 +57,7 @@ LanguageClientManager::LanguageClientManager(QObject *parent)
|
||||
using namespace Core;
|
||||
using namespace ProjectExplorer;
|
||||
JsonRpcMessageHandler::registerMessageProvider<PublishDiagnosticsNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<SemanticHighlightNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ApplyWorkspaceEditRequest>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<LogMessageNotification>();
|
||||
JsonRpcMessageHandler::registerMessageProvider<ShowMessageRequest>();
|
||||
|
161
src/plugins/languageclient/semantichighlightsupport.cpp
Normal file
161
src/plugins/languageclient/semantichighlightsupport.cpp
Normal file
@@ -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 <QTextDocument>
|
||||
|
||||
using namespace LanguageServerProtocol;
|
||||
|
||||
namespace LanguageClient {
|
||||
namespace SemanticHighligtingSupport {
|
||||
|
||||
static Q_LOGGING_CATEGORY(LOGLSPHIGHLIGHT, "qtc.languageclient.highlight", QtWarningMsg);
|
||||
|
||||
static const QList<QList<QString>> highlightScopes(const ServerCapabilities &capabilities)
|
||||
{
|
||||
return capabilities.semanticHighlighting()
|
||||
.value_or(ServerCapabilities::SemanticHighlightingServerCapabilities())
|
||||
.scopes().value_or(QList<QList<QString>>());
|
||||
}
|
||||
|
||||
static Utils::optional<TextEditor::TextStyle> styleForScopes(const QList<QString> &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<QString, TextEditor::TextStyle> 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<int, QTextCharFormat> scopesToFormatHash(QList<QList<QString>> scopes,
|
||||
const TextEditor::FontSettings &fontSettings)
|
||||
{
|
||||
QHash<int, QTextCharFormat> scopesToFormat;
|
||||
for (int i = 0; i < scopes.size(); ++i) {
|
||||
if (Utils::optional<TextEditor::TextStyle> 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<SemanticHighlightingInformation> &lines)
|
||||
{
|
||||
TextEditor::HighlightingResults results;
|
||||
|
||||
for (const SemanticHighlightingInformation &info : lines) {
|
||||
const int line = info.line();
|
||||
for (const SemanticHighlightToken &token :
|
||||
info.tokens().value_or(QList<SemanticHighlightToken>())) {
|
||||
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
|
46
src/plugins/languageclient/semantichighlightsupport.h
Normal file
46
src/plugins/languageclient/semantichighlightsupport.h
Normal file
@@ -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 <languageserverprotocol/languagefeatures.h>
|
||||
#include <languageserverprotocol/servercapabilities.h>
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <texteditor/textdocument.h>
|
||||
|
||||
namespace LanguageClient {
|
||||
namespace SemanticHighligtingSupport {
|
||||
|
||||
TextEditor::HighlightingResults generateResults(
|
||||
const QList<LanguageServerProtocol::SemanticHighlightingInformation> &lines);
|
||||
|
||||
void applyHighlight(TextEditor::TextDocument *doc,
|
||||
const TextEditor::HighlightingResults &results,
|
||||
const LanguageServerProtocol::ServerCapabilities &capabilities);
|
||||
|
||||
} // namespace SemanticHighligtingSupport
|
||||
} // namespace LanguageClient
|
@@ -121,6 +121,8 @@ void SemanticHighlighter::setExtraAdditionalFormats(SyntaxHighlighter *highlight
|
||||
const QList<HighlightingResult> &results,
|
||||
const QHash<int, QTextCharFormat> &kindToFormat)
|
||||
{
|
||||
if (!highlighter)
|
||||
return;
|
||||
highlighter->clearAllExtraFormats();
|
||||
|
||||
QTextDocument *doc = highlighter->document();
|
||||
|
@@ -71,6 +71,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using HighlightingResults = QList<HighlightingResult>;
|
||||
|
||||
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<HighlightingResult> &results,
|
||||
const HighlightingResults &results,
|
||||
const QHash<int, QTextCharFormat> &kindToFormat);
|
||||
|
||||
// Cleans the extra additional formats after the last result of the Future
|
||||
|
@@ -25,6 +25,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "texteditor_global.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
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";
|
||||
|
Reference in New Issue
Block a user