2019-06-12 12:55:06 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** 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"
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
#include "client.h"
|
|
|
|
|
#include "languageclientmanager.h"
|
|
|
|
|
|
|
|
|
|
#include <texteditor/fontsettings.h>
|
|
|
|
|
#include <texteditor/texteditor.h>
|
|
|
|
|
#include <texteditor/texteditorsettings.h>
|
|
|
|
|
#include <utils/mimetypes/mimedatabase.h>
|
|
|
|
|
|
2019-06-12 12:55:06 +02:00
|
|
|
#include <QTextDocument>
|
|
|
|
|
|
|
|
|
|
using namespace LanguageServerProtocol;
|
2021-02-25 12:54:14 +01:00
|
|
|
using namespace TextEditor;
|
2019-06-12 12:55:06 +02:00
|
|
|
|
|
|
|
|
namespace LanguageClient {
|
|
|
|
|
|
|
|
|
|
static Q_LOGGING_CATEGORY(LOGLSPHIGHLIGHT, "qtc.languageclient.highlight", QtWarningMsg);
|
|
|
|
|
|
2021-07-05 09:45:44 +02:00
|
|
|
namespace SemanticHighligtingSupport {
|
|
|
|
|
|
2019-06-12 12:55:06 +02:00
|
|
|
static const QList<QList<QString>> highlightScopes(const ServerCapabilities &capabilities)
|
|
|
|
|
{
|
|
|
|
|
return capabilities.semanticHighlighting()
|
|
|
|
|
.value_or(ServerCapabilities::SemanticHighlightingServerCapabilities())
|
|
|
|
|
.scopes().value_or(QList<QList<QString>>());
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
static Utils::optional<TextStyle> styleForScopes(const QList<QString> &scopes)
|
2019-06-12 12:55:06 +02:00
|
|
|
{
|
|
|
|
|
// 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
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
static const QMap<QString, TextStyle> styleForScopes = {
|
|
|
|
|
{"entity.name", C_TYPE},
|
|
|
|
|
{"entity.name.function", C_FUNCTION},
|
|
|
|
|
{"entity.name.function.method.static", C_GLOBAL},
|
|
|
|
|
{"entity.name.function.preprocessor", C_PREPROCESSOR},
|
|
|
|
|
{"entity.name.label", C_LABEL},
|
|
|
|
|
{"keyword", C_KEYWORD},
|
|
|
|
|
{"storage.type", C_KEYWORD},
|
|
|
|
|
{"constant.numeric", C_NUMBER},
|
|
|
|
|
{"string", C_STRING},
|
|
|
|
|
{"comment", C_COMMENT},
|
|
|
|
|
{"comment.block.documentation", C_DOXYGEN_COMMENT},
|
|
|
|
|
{"variable.function", C_FUNCTION},
|
|
|
|
|
{"variable.other", C_LOCAL},
|
|
|
|
|
{"variable.other.member", C_FIELD},
|
|
|
|
|
{"variable.other.field", C_FIELD},
|
|
|
|
|
{"variable.other.field.static", C_GLOBAL},
|
|
|
|
|
{"variable.parameter", C_PARAMETER},
|
2019-06-12 12:55:06 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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,
|
2021-02-25 12:54:14 +01:00
|
|
|
const FontSettings &fontSettings)
|
2019-06-12 12:55:06 +02:00
|
|
|
{
|
|
|
|
|
QHash<int, QTextCharFormat> scopesToFormat;
|
|
|
|
|
for (int i = 0; i < scopes.size(); ++i) {
|
2021-02-25 12:54:14 +01:00
|
|
|
if (Utils::optional<TextStyle> style = styleForScopes(scopes[i]))
|
2019-06-12 12:55:06 +02:00
|
|
|
scopesToFormat[i] = fontSettings.toTextCharFormat(style.value());
|
|
|
|
|
}
|
|
|
|
|
return scopesToFormat;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
HighlightingResult tokenToHighlightingResult(int line, const SemanticHighlightToken &token)
|
2019-06-12 12:55:06 +02:00
|
|
|
{
|
2021-02-25 12:54:14 +01:00
|
|
|
return HighlightingResult(unsigned(line) + 1,
|
|
|
|
|
unsigned(token.character) + 1,
|
|
|
|
|
token.length,
|
|
|
|
|
int(token.scope));
|
2019-06-12 12:55:06 +02:00
|
|
|
}
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
HighlightingResults generateResults(const QList<SemanticHighlightingInformation> &lines)
|
2019-06-12 12:55:06 +02:00
|
|
|
{
|
2021-02-25 12:54:14 +01:00
|
|
|
HighlightingResults results;
|
2019-06-12 12:55:06 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
void applyHighlight(TextDocument *doc,
|
|
|
|
|
const HighlightingResults &results,
|
2019-06-12 12:55:06 +02:00
|
|
|
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];
|
2021-02-25 12:54:14 +01:00
|
|
|
auto style = styleForScopes(resultScupes).value_or(C_TEXT);
|
2019-06-12 12:55:06 +02:00
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << result.line - 1 << '\t'
|
|
|
|
|
<< result.column - 1 << '\t'
|
|
|
|
|
<< result.length << '\t'
|
|
|
|
|
<< TextEditor::Constants::nameForStyle(style) << '\t'
|
|
|
|
|
<< text
|
|
|
|
|
<< resultScupes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-17 12:57:59 +01:00
|
|
|
if (capabilities.semanticHighlighting().has_value()) {
|
2021-02-25 12:54:14 +01:00
|
|
|
SemanticHighlighter::setExtraAdditionalFormats(
|
2020-02-17 12:57:59 +01:00
|
|
|
doc->syntaxHighlighter(),
|
|
|
|
|
results,
|
|
|
|
|
scopesToFormatHash(highlightScopes(capabilities), doc->fontSettings()));
|
|
|
|
|
}
|
2019-06-12 12:55:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace SemanticHighligtingSupport
|
2021-02-25 12:54:14 +01:00
|
|
|
|
|
|
|
|
constexpr int tokenTypeBitOffset = 16;
|
|
|
|
|
|
|
|
|
|
SemanticTokenSupport::SemanticTokenSupport(Client *client)
|
|
|
|
|
: m_client(client)
|
|
|
|
|
{
|
|
|
|
|
QObject::connect(TextEditorSettings::instance(),
|
|
|
|
|
&TextEditorSettings::fontSettingsChanged,
|
|
|
|
|
client,
|
|
|
|
|
[this]() { updateFormatHash(); });
|
2021-11-02 14:21:50 +01:00
|
|
|
QObject::connect(Core::EditorManager::instance(),
|
|
|
|
|
&Core::EditorManager::currentEditorChanged,
|
|
|
|
|
this,
|
|
|
|
|
&SemanticTokenSupport::onCurrentEditorChanged);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::refresh()
|
|
|
|
|
{
|
|
|
|
|
m_tokens.clear();
|
|
|
|
|
for (Core::IEditor *editor : Core::EditorManager::visibleEditors())
|
|
|
|
|
onCurrentEditorChanged(editor);
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::reloadSemanticTokens(TextDocument *textDocument)
|
|
|
|
|
{
|
|
|
|
|
const SemanticRequestTypes supportedRequests = supportedSemanticRequests(textDocument);
|
|
|
|
|
if (supportedRequests.testFlag(SemanticRequestType::None))
|
|
|
|
|
return;
|
|
|
|
|
const Utils::FilePath filePath = textDocument->filePath();
|
|
|
|
|
const TextDocumentIdentifier docId(DocumentUri::fromFilePath(filePath));
|
2021-10-22 10:52:48 +02:00
|
|
|
auto responseCallback = [this, filePath, documentVersion = m_client->documentVersion(filePath)](
|
|
|
|
|
const SemanticTokensFullRequest::Response &response) {
|
|
|
|
|
handleSemanticTokens(filePath, response.result().value_or(nullptr), documentVersion);
|
2021-02-25 12:54:14 +01:00
|
|
|
};
|
|
|
|
|
/*if (supportedRequests.testFlag(SemanticRequestType::Range)) {
|
|
|
|
|
const int start = widget->firstVisibleBlockNumber();
|
|
|
|
|
const int end = widget->lastVisibleBlockNumber();
|
|
|
|
|
const int pageSize = end - start;
|
|
|
|
|
// request one extra page upfront and after the current visible range
|
|
|
|
|
Range range(Position(qMax(0, start - pageSize), 0),
|
|
|
|
|
Position(qMin(widget->blockCount() - 1, end + pageSize), 0));
|
|
|
|
|
SemanticTokensRangeParams params;
|
|
|
|
|
params.setTextDocument(docId);
|
|
|
|
|
params.setRange(range);
|
|
|
|
|
SemanticTokensRangeRequest request(params);
|
|
|
|
|
request.setResponseCallback(responseCallback);
|
|
|
|
|
m_client->sendContent(request);
|
|
|
|
|
} else */
|
|
|
|
|
if (supportedRequests.testFlag(SemanticRequestType::Full)) {
|
|
|
|
|
SemanticTokensParams params;
|
|
|
|
|
params.setTextDocument(docId);
|
|
|
|
|
SemanticTokensFullRequest request(params);
|
|
|
|
|
request.setResponseCallback(responseCallback);
|
|
|
|
|
m_client->sendContent(request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::updateSemanticTokens(TextDocument *textDocument)
|
|
|
|
|
{
|
|
|
|
|
const SemanticRequestTypes supportedRequests = supportedSemanticRequests(textDocument);
|
|
|
|
|
if (supportedRequests.testFlag(SemanticRequestType::FullDelta)) {
|
|
|
|
|
const Utils::FilePath filePath = textDocument->filePath();
|
2021-11-02 14:21:50 +01:00
|
|
|
const VersionedTokens versionedToken = m_tokens.value(filePath);
|
|
|
|
|
const QString &previousResultId = versionedToken.tokens.resultId().value_or(QString());
|
2021-02-25 12:54:14 +01:00
|
|
|
if (!previousResultId.isEmpty()) {
|
2021-11-02 14:21:50 +01:00
|
|
|
if (m_client->documentVersion(filePath) == versionedToken.version)
|
|
|
|
|
return;
|
2021-02-25 12:54:14 +01:00
|
|
|
SemanticTokensDeltaParams params;
|
|
|
|
|
params.setTextDocument(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)));
|
|
|
|
|
params.setPreviousResultId(previousResultId);
|
|
|
|
|
SemanticTokensFullDeltaRequest request(params);
|
|
|
|
|
request.setResponseCallback(
|
2021-10-22 10:52:48 +02:00
|
|
|
[this, filePath, documentVersion = m_client->documentVersion(filePath)](
|
|
|
|
|
const SemanticTokensFullDeltaRequest::Response &response) {
|
|
|
|
|
handleSemanticTokensDelta(filePath,
|
|
|
|
|
response.result().value_or(nullptr),
|
|
|
|
|
documentVersion);
|
2021-02-25 12:54:14 +01:00
|
|
|
});
|
|
|
|
|
m_client->sendContent(request);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
reloadSemanticTokens(textDocument);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::rehighlight()
|
|
|
|
|
{
|
|
|
|
|
for (const Utils::FilePath &filePath : m_tokens.keys())
|
2021-11-15 14:58:40 +01:00
|
|
|
highlight(filePath, true);
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void addModifiers(int key,
|
|
|
|
|
QHash<int, QTextCharFormat> *formatHash,
|
|
|
|
|
TextStyles styles,
|
|
|
|
|
QList<int> tokenModifiers,
|
|
|
|
|
const TextEditor::FontSettings &fs)
|
|
|
|
|
{
|
|
|
|
|
if (tokenModifiers.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
int modifier = tokenModifiers.takeLast();
|
2021-06-10 08:43:24 +02:00
|
|
|
if (modifier < 0)
|
|
|
|
|
return;
|
|
|
|
|
auto addModifier = [&](TextStyle style) {
|
2021-02-25 12:54:14 +01:00
|
|
|
if (key & modifier) // already there don't add twice
|
|
|
|
|
return;
|
|
|
|
|
key = key | modifier;
|
|
|
|
|
styles.mixinStyles.push_back(style);
|
|
|
|
|
formatHash->insert(key, fs.toTextCharFormat(styles));
|
|
|
|
|
};
|
|
|
|
|
switch (modifier) {
|
|
|
|
|
case declarationModifier: addModifier(C_DECLARATION); break;
|
|
|
|
|
case definitionModifier: addModifier(C_FUNCTION_DEFINITION); break;
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
addModifiers(key, formatHash, styles, tokenModifiers, fs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::setLegend(const LanguageServerProtocol::SemanticTokensLegend &legend)
|
|
|
|
|
{
|
2021-06-09 09:47:26 +02:00
|
|
|
m_tokenTypeStrings = legend.tokenTypes();
|
|
|
|
|
m_tokenModifierStrings = legend.tokenModifiers();
|
2021-02-25 12:54:14 +01:00
|
|
|
m_tokenTypes = Utils::transform(legend.tokenTypes(), [&](const QString &tokenTypeString){
|
|
|
|
|
return m_tokenTypesMap.value(tokenTypeString, -1);
|
|
|
|
|
});
|
|
|
|
|
m_tokenModifiers = Utils::transform(legend.tokenModifiers(), [&](const QString &tokenModifierString){
|
|
|
|
|
return m_tokenModifiersMap.value(tokenModifierString, -1);
|
|
|
|
|
});
|
|
|
|
|
updateFormatHash();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::updateFormatHash()
|
|
|
|
|
{
|
|
|
|
|
auto fontSettings = TextEditorSettings::fontSettings();
|
|
|
|
|
for (int tokenType : qAsConst(m_tokenTypes)) {
|
|
|
|
|
if (tokenType < 0)
|
|
|
|
|
continue;
|
|
|
|
|
TextStyle style;
|
|
|
|
|
switch (tokenType) {
|
|
|
|
|
case typeToken: style = C_TYPE; break;
|
|
|
|
|
case classToken: style = C_TYPE; break;
|
|
|
|
|
case enumMemberToken: style = C_ENUMERATION; break;
|
|
|
|
|
case typeParameterToken: style = C_FIELD; break;
|
|
|
|
|
case parameterToken: style = C_PARAMETER; break;
|
|
|
|
|
case variableToken: style = C_LOCAL; break;
|
|
|
|
|
case functionToken: style = C_FUNCTION; break;
|
2021-06-10 09:13:40 +02:00
|
|
|
case methodToken: style = C_FUNCTION; break;
|
2021-02-25 12:54:14 +01:00
|
|
|
case macroToken: style = C_PREPROCESSOR; break;
|
|
|
|
|
case keywordToken: style = C_KEYWORD; break;
|
|
|
|
|
case commentToken: style = C_COMMENT; break;
|
|
|
|
|
case stringToken: style = C_STRING; break;
|
|
|
|
|
case numberToken: style = C_NUMBER; break;
|
|
|
|
|
case operatorToken: style = C_OPERATOR; break;
|
|
|
|
|
default:
|
|
|
|
|
style = m_additionalTypeStyles.value(tokenType, C_TEXT);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
int mainHashPart = tokenType << tokenTypeBitOffset;
|
|
|
|
|
m_formatHash[mainHashPart] = fontSettings.toTextCharFormat(style);
|
|
|
|
|
TextStyles styles;
|
|
|
|
|
styles.mainStyle = style;
|
|
|
|
|
styles.mixinStyles.initializeElements();
|
|
|
|
|
addModifiers(mainHashPart, &m_formatHash, styles, m_tokenModifiers, fontSettings);
|
|
|
|
|
}
|
|
|
|
|
rehighlight();
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-02 14:21:50 +01:00
|
|
|
void SemanticTokenSupport::onCurrentEditorChanged(Core::IEditor *editor)
|
|
|
|
|
{
|
|
|
|
|
if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
|
|
|
|
|
updateSemanticTokens(textEditor->textDocument());
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 12:54:14 +01:00
|
|
|
void SemanticTokenSupport::setTokenTypesMap(const QMap<QString, int> &tokenTypesMap)
|
|
|
|
|
{
|
|
|
|
|
m_tokenTypesMap = tokenTypesMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::setTokenModifiersMap(const QMap<QString, int> &tokenModifiersMap)
|
|
|
|
|
{
|
|
|
|
|
m_tokenModifiersMap = tokenModifiersMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::setAdditionalTokenTypeStyles(
|
|
|
|
|
const QHash<int, TextStyle> &typeStyles)
|
|
|
|
|
{
|
|
|
|
|
m_additionalTypeStyles = typeStyles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//void SemanticTokenSupport::setAdditionalTokenModifierStyles(
|
|
|
|
|
// const QHash<int, TextStyle> &modifierStyles)
|
|
|
|
|
//{
|
|
|
|
|
// m_additionalModifierStyles = modifierStyles;
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
SemanticRequestTypes SemanticTokenSupport::supportedSemanticRequests(TextDocument *document) const
|
|
|
|
|
{
|
2021-11-15 14:42:25 +01:00
|
|
|
if (!m_client->documentOpen(document))
|
|
|
|
|
return SemanticRequestType::None;
|
2021-02-25 12:54:14 +01:00
|
|
|
auto supportedRequests = [&](const QJsonObject &options) -> SemanticRequestTypes {
|
|
|
|
|
TextDocumentRegistrationOptions docOptions(options);
|
|
|
|
|
if (docOptions.isValid()
|
|
|
|
|
&& docOptions.filterApplies(document->filePath(),
|
|
|
|
|
Utils::mimeTypeForName(document->mimeType()))) {
|
|
|
|
|
return SemanticRequestType::None;
|
|
|
|
|
}
|
|
|
|
|
const SemanticTokensOptions semanticOptions(options);
|
|
|
|
|
return semanticOptions.supportedRequests();
|
|
|
|
|
};
|
|
|
|
|
const QString dynamicMethod = "textDocument/semanticTokens";
|
|
|
|
|
const DynamicCapabilities &dynamicCapabilities = m_client->dynamicCapabilities();
|
|
|
|
|
if (auto registered = dynamicCapabilities.isRegistered(dynamicMethod);
|
|
|
|
|
registered.has_value()) {
|
|
|
|
|
if (!registered.value())
|
|
|
|
|
return SemanticRequestType::None;
|
|
|
|
|
return supportedRequests(dynamicCapabilities.option(dynamicMethod).toObject());
|
|
|
|
|
}
|
|
|
|
|
if (m_client->capabilities().semanticTokensProvider().has_value())
|
|
|
|
|
return supportedRequests(m_client->capabilities().semanticTokensProvider().value());
|
|
|
|
|
return SemanticRequestType::None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::handleSemanticTokens(const Utils::FilePath &filePath,
|
2021-10-22 10:52:48 +02:00
|
|
|
const SemanticTokensResult &result,
|
|
|
|
|
int documentVersion)
|
2021-02-25 12:54:14 +01:00
|
|
|
{
|
2021-07-08 12:00:13 +02:00
|
|
|
if (auto tokens = Utils::get_if<SemanticTokens>(&result)) {
|
2021-10-22 10:52:48 +02:00
|
|
|
m_tokens[filePath] = {*tokens, documentVersion};
|
2021-07-08 12:00:13 +02:00
|
|
|
highlight(filePath);
|
2021-08-09 14:44:34 +02:00
|
|
|
} else {
|
|
|
|
|
m_tokens.remove(filePath);
|
2021-07-08 12:00:13 +02:00
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SemanticTokenSupport::handleSemanticTokensDelta(
|
2021-10-22 10:52:48 +02:00
|
|
|
const Utils::FilePath &filePath,
|
|
|
|
|
const LanguageServerProtocol::SemanticTokensDeltaResult &result,
|
|
|
|
|
int documentVersion)
|
2021-02-25 12:54:14 +01:00
|
|
|
{
|
|
|
|
|
if (auto tokens = Utils::get_if<SemanticTokens>(&result)) {
|
2021-10-22 10:52:48 +02:00
|
|
|
m_tokens[filePath] = {*tokens, documentVersion};
|
2021-02-25 12:54:14 +01:00
|
|
|
} else if (auto tokensDelta = Utils::get_if<SemanticTokensDelta>(&result)) {
|
2021-10-22 15:48:59 +02:00
|
|
|
m_tokens[filePath].version = documentVersion;
|
2021-05-06 12:39:37 +02:00
|
|
|
QList<SemanticTokensEdit> edits = tokensDelta->edits();
|
2021-06-18 16:30:03 +02:00
|
|
|
if (edits.isEmpty()) {
|
2021-09-06 17:02:36 +02:00
|
|
|
highlight(filePath);
|
2021-02-25 12:54:14 +01:00
|
|
|
return;
|
2021-06-18 16:30:03 +02:00
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
|
2021-05-06 12:39:37 +02:00
|
|
|
Utils::sort(edits, &SemanticTokensEdit::start);
|
|
|
|
|
|
2021-10-22 10:52:48 +02:00
|
|
|
SemanticTokens &tokens = m_tokens[filePath].tokens;
|
2021-05-06 12:39:37 +02:00
|
|
|
const QList<int> &data = tokens.data();
|
2021-02-25 12:54:14 +01:00
|
|
|
|
|
|
|
|
int newDataSize = data.size();
|
2021-05-06 12:39:37 +02:00
|
|
|
for (const SemanticTokensEdit &edit : qAsConst(edits))
|
2021-02-25 12:54:14 +01:00
|
|
|
newDataSize += edit.dataSize() - edit.deleteCount();
|
|
|
|
|
QList<int> newData;
|
|
|
|
|
newData.reserve(newDataSize);
|
|
|
|
|
|
|
|
|
|
auto it = data.begin();
|
2021-07-05 09:45:44 +02:00
|
|
|
const auto end = data.end();
|
2021-10-29 11:34:45 +02:00
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << "Edit Tokens for " << filePath;
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << "Data before edit " << data;
|
2021-05-06 12:39:37 +02:00
|
|
|
for (const SemanticTokensEdit &edit : qAsConst(edits)) {
|
|
|
|
|
if (edit.start() > data.size()) // prevent edits after the previously reported data
|
|
|
|
|
return;
|
|
|
|
|
for (const auto start = data.begin() + edit.start(); it < start; ++it)
|
2021-02-25 12:54:14 +01:00
|
|
|
newData.append(*it);
|
2021-10-29 11:34:45 +02:00
|
|
|
const Utils::optional<QList<int>> editData = edit.data();
|
|
|
|
|
if (editData.has_value()) {
|
|
|
|
|
newData.append(editData.value());
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << edit.start() << edit.deleteCount() << editData.value();
|
|
|
|
|
} else {
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << edit.start() << edit.deleteCount();
|
|
|
|
|
}
|
2021-07-05 09:45:44 +02:00
|
|
|
int deleteCount = edit.deleteCount();
|
|
|
|
|
if (deleteCount > std::distance(it, end)) {
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT)
|
|
|
|
|
<< "We shall delete more highlight data entries than we actually have, "
|
|
|
|
|
"so we are out of sync with the server. "
|
|
|
|
|
"Request full semantic tokens again.";
|
|
|
|
|
TextDocument *doc = TextDocument::textDocumentForFilePath(filePath);
|
|
|
|
|
if (doc && LanguageClientManager::clientForDocument(doc) == m_client)
|
|
|
|
|
reloadSemanticTokens(doc);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
it += deleteCount;
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
2021-07-05 09:45:44 +02:00
|
|
|
for (; it != end; ++it)
|
2021-02-25 12:54:14 +01:00
|
|
|
newData.append(*it);
|
|
|
|
|
|
2021-10-29 11:34:45 +02:00
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << "New Data " << newData;
|
2021-02-25 12:54:14 +01:00
|
|
|
tokens.setData(newData);
|
|
|
|
|
tokens.setResultId(tokensDelta->resultId());
|
|
|
|
|
} else {
|
|
|
|
|
m_tokens.remove(filePath);
|
2021-07-08 12:00:13 +02:00
|
|
|
return;
|
2021-02-25 12:54:14 +01:00
|
|
|
}
|
|
|
|
|
highlight(filePath);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 14:58:40 +01:00
|
|
|
void SemanticTokenSupport::highlight(const Utils::FilePath &filePath, bool force)
|
2021-02-25 12:54:14 +01:00
|
|
|
{
|
|
|
|
|
TextDocument *doc = TextDocument::textDocumentForFilePath(filePath);
|
|
|
|
|
if (!doc || LanguageClientManager::clientForDocument(doc) != m_client)
|
|
|
|
|
return;
|
|
|
|
|
SyntaxHighlighter *highlighter = doc->syntaxHighlighter();
|
|
|
|
|
if (!highlighter)
|
|
|
|
|
return;
|
2021-10-22 15:06:46 +02:00
|
|
|
const VersionedTokens versionedTokens = m_tokens.value(filePath);
|
|
|
|
|
const QList<SemanticToken> tokens = versionedTokens.tokens
|
|
|
|
|
.toTokens(m_tokenTypes, m_tokenModifiers);
|
2021-06-09 09:47:26 +02:00
|
|
|
if (m_tokensHandler) {
|
|
|
|
|
int line = 1;
|
|
|
|
|
int column = 1;
|
|
|
|
|
QList<ExpandedSemanticToken> expandedTokens;
|
|
|
|
|
for (const SemanticToken &token : tokens) {
|
|
|
|
|
line += token.deltaLine;
|
|
|
|
|
if (token.deltaLine != 0) // reset the current column when we change the current line
|
|
|
|
|
column = 1;
|
|
|
|
|
column += token.deltaStart;
|
|
|
|
|
if (token.tokenIndex >= m_tokenTypeStrings.length())
|
|
|
|
|
continue;
|
|
|
|
|
ExpandedSemanticToken expandedToken;
|
|
|
|
|
expandedToken.type = m_tokenTypeStrings.at(token.tokenIndex);
|
|
|
|
|
int modifiers = token.rawTokenModifiers;
|
|
|
|
|
for (int bitPos = 0; modifiers && bitPos < m_tokenModifierStrings.length();
|
|
|
|
|
++bitPos, modifiers >>= 1) {
|
|
|
|
|
if (modifiers & 0x1)
|
|
|
|
|
expandedToken.modifiers << m_tokenModifierStrings.at(bitPos);
|
|
|
|
|
}
|
|
|
|
|
expandedToken.line = line;
|
|
|
|
|
expandedToken.column = column;
|
|
|
|
|
expandedToken.length = token.length;
|
|
|
|
|
expandedTokens << expandedToken;
|
|
|
|
|
};
|
2021-10-29 11:34:45 +02:00
|
|
|
if (LOGLSPHIGHLIGHT().isDebugEnabled()) {
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT) << "Expanded Tokens for " << filePath;
|
|
|
|
|
for (const ExpandedSemanticToken &token : qAsConst(expandedTokens)) {
|
|
|
|
|
qCDebug(LOGLSPHIGHLIGHT)
|
|
|
|
|
<< token.line << token.column << token.length << token.type << token.modifiers;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 14:58:40 +01:00
|
|
|
m_tokensHandler(doc, expandedTokens, versionedTokens.version, force);
|
2021-06-09 09:47:26 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2021-02-25 12:54:14 +01:00
|
|
|
int line = 1;
|
|
|
|
|
int column = 1;
|
|
|
|
|
auto toResult = [&](const SemanticToken &token){
|
|
|
|
|
line += token.deltaLine;
|
|
|
|
|
if (token.deltaLine != 0) // reset the current column when we change the current line
|
|
|
|
|
column = 1;
|
|
|
|
|
column += token.deltaStart;
|
|
|
|
|
const int tokenKind = token.tokenType << tokenTypeBitOffset | token.tokenModifiers;
|
|
|
|
|
return HighlightingResult(line, column, token.length, tokenKind);
|
|
|
|
|
};
|
|
|
|
|
const HighlightingResults results = Utils::transform(tokens, toResult);
|
|
|
|
|
SemanticHighlighter::setExtraAdditionalFormats(highlighter, results, m_formatHash);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-12 12:55:06 +02:00
|
|
|
} // namespace LanguageClient
|