mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-21 23:25:07 +00:00
Support LSP semantic tokens
This patch implements `textDocument/semanticTokens/{full,range}`. If the client supports semantic tokens, $ccls/publishSemanticHighlight (now deprecated) is disabled. These token modifiers are mostly useful to emphasize certain symbols: `static, classScope, globalScope, namespaceScope`. To enable a colorful syntax highlighting scheme, set the highlight.rainbow initialization option to 10. https://maskray.me/blog/2024-10-20-ccls-and-lsp-semantic-tokens Note that the older $ccls/publishSemanticHighlight protocol with highlight.lsRanges==true (used by vscode-ccls) is no longer supported.
This commit is contained in:
parent
50fd8d069e
commit
cc13ced659
@ -117,6 +117,8 @@ struct Config {
|
|||||||
bool hierarchicalDocumentSymbolSupport = true;
|
bool hierarchicalDocumentSymbolSupport = true;
|
||||||
// TextDocumentClientCapabilities.definition.linkSupport
|
// TextDocumentClientCapabilities.definition.linkSupport
|
||||||
bool linkSupport = true;
|
bool linkSupport = true;
|
||||||
|
// ClientCapabilities.workspace.semanticTokens.refreshSupport
|
||||||
|
bool semanticTokensRefresh = true;
|
||||||
|
|
||||||
// If false, disable snippets and complete just the identifier part.
|
// If false, disable snippets and complete just the identifier part.
|
||||||
// TextDocumentClientCapabilities.completion.completionItem.snippetSupport
|
// TextDocumentClientCapabilities.completion.completionItem.snippetSupport
|
||||||
@ -226,8 +228,9 @@ struct Config {
|
|||||||
// Disable semantic highlighting for files larger than the size.
|
// Disable semantic highlighting for files larger than the size.
|
||||||
int64_t largeFileSize = 2 * 1024 * 1024;
|
int64_t largeFileSize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
// true: LSP line/character; false: position
|
// If non-zero, enable rainbow semantic tokens by assinging an extra modifier
|
||||||
bool lsRanges = false;
|
// indicating the rainbow ID to each symbol.
|
||||||
|
int rainbow = 0;
|
||||||
|
|
||||||
// Like index.{whitelist,blacklist}, don't publish semantic highlighting to
|
// Like index.{whitelist,blacklist}, don't publish semantic highlighting to
|
||||||
// blacklisted files.
|
// blacklisted files.
|
||||||
@ -342,7 +345,7 @@ REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel,
|
|||||||
maxNum, placeholder);
|
maxNum, placeholder);
|
||||||
REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
|
REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
|
||||||
spellChecking, whitelist)
|
spellChecking, whitelist)
|
||||||
REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist)
|
REFLECT_STRUCT(Config::Highlight, largeFileSize, rainbow, blacklist, whitelist)
|
||||||
REFLECT_STRUCT(Config::Index::Name, suppressUnwrittenScope);
|
REFLECT_STRUCT(Config::Index::Name, suppressUnwrittenScope);
|
||||||
REFLECT_STRUCT(Config::Index, blacklist, comments, initialNoLinkage,
|
REFLECT_STRUCT(Config::Index, blacklist, comments, initialNoLinkage,
|
||||||
initialBlacklist, initialWhitelist, maxInitializerLines,
|
initialBlacklist, initialWhitelist, maxInitializerLines,
|
||||||
|
36
src/enum.inc
Normal file
36
src/enum.inc
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#ifndef TOKEN_MODIFIER
|
||||||
|
#define TOKEN_MODIFIER(name, str)
|
||||||
|
#endif
|
||||||
|
// vscode
|
||||||
|
TOKEN_MODIFIER(Declaration, "declaration")
|
||||||
|
TOKEN_MODIFIER(Definition, "definition")
|
||||||
|
TOKEN_MODIFIER(Static, "static")
|
||||||
|
|
||||||
|
// ccls extensions
|
||||||
|
TOKEN_MODIFIER(Read, "read")
|
||||||
|
TOKEN_MODIFIER(Write, "write")
|
||||||
|
TOKEN_MODIFIER(ClassScope, "classScope")
|
||||||
|
TOKEN_MODIFIER(FunctionScope, "functionScope")
|
||||||
|
TOKEN_MODIFIER(NamespaceScope, "namespaceScope")
|
||||||
|
|
||||||
|
// Rainbow semantic tokens
|
||||||
|
TOKEN_MODIFIER(Id0, "id0")
|
||||||
|
TOKEN_MODIFIER(Id1, "id1")
|
||||||
|
TOKEN_MODIFIER(Id2, "id2")
|
||||||
|
TOKEN_MODIFIER(Id3, "id3")
|
||||||
|
TOKEN_MODIFIER(Id4, "id4")
|
||||||
|
TOKEN_MODIFIER(Id5, "id5")
|
||||||
|
TOKEN_MODIFIER(Id6, "id6")
|
||||||
|
TOKEN_MODIFIER(Id7, "id7")
|
||||||
|
TOKEN_MODIFIER(Id8, "id8")
|
||||||
|
TOKEN_MODIFIER(Id9, "id9")
|
||||||
|
TOKEN_MODIFIER(Id10, "id10")
|
||||||
|
TOKEN_MODIFIER(Id11, "id11")
|
||||||
|
TOKEN_MODIFIER(Id12, "id12")
|
||||||
|
TOKEN_MODIFIER(Id13, "id13")
|
||||||
|
TOKEN_MODIFIER(Id14, "id14")
|
||||||
|
TOKEN_MODIFIER(Id15, "id15")
|
||||||
|
TOKEN_MODIFIER(Id16, "id16")
|
||||||
|
TOKEN_MODIFIER(Id17, "id17")
|
||||||
|
TOKEN_MODIFIER(Id18, "id18")
|
||||||
|
TOKEN_MODIFIER(Id19, "id19")
|
@ -132,6 +132,12 @@ void reflect(BinaryWriter &visitor, SymbolRef &value);
|
|||||||
void reflect(BinaryWriter &visitor, Use &value);
|
void reflect(BinaryWriter &visitor, Use &value);
|
||||||
void reflect(BinaryWriter &visitor, DeclRef &value);
|
void reflect(BinaryWriter &visitor, DeclRef &value);
|
||||||
|
|
||||||
|
enum class TokenModifier {
|
||||||
|
#define TOKEN_MODIFIER(name, str) name,
|
||||||
|
#include "enum.inc"
|
||||||
|
#undef TOKEN_MODIFIER
|
||||||
|
};
|
||||||
|
|
||||||
template <typename T> using VectorAdapter = std::vector<T, std::allocator<T>>;
|
template <typename T> using VectorAdapter = std::vector<T, std::allocator<T>>;
|
||||||
|
|
||||||
template <typename D> struct NameMixin {
|
template <typename D> struct NameMixin {
|
||||||
|
@ -166,6 +166,7 @@ enum class SymbolKind : uint8_t {
|
|||||||
// For C++, this is interpreted as "template parameter" (including
|
// For C++, this is interpreted as "template parameter" (including
|
||||||
// non-type template parameters).
|
// non-type template parameters).
|
||||||
TypeParameter = 26,
|
TypeParameter = 26,
|
||||||
|
FirstNonStandard,
|
||||||
|
|
||||||
// ccls extensions
|
// ccls extensions
|
||||||
// See also https://github.com/Microsoft/language-server-protocol/issues/344
|
// See also https://github.com/Microsoft/language-server-protocol/issues/344
|
||||||
@ -174,6 +175,8 @@ enum class SymbolKind : uint8_t {
|
|||||||
Parameter = 253,
|
Parameter = 253,
|
||||||
StaticMethod = 254,
|
StaticMethod = 254,
|
||||||
Macro = 255,
|
Macro = 255,
|
||||||
|
FirstExtension = TypeAlias,
|
||||||
|
LastExtension = Macro,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SymbolInformation {
|
struct SymbolInformation {
|
||||||
|
@ -11,11 +11,25 @@
|
|||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/reader.h>
|
#include <rapidjson/reader.h>
|
||||||
|
|
||||||
|
#include <llvm/ADT/STLExtras.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
using namespace clang;
|
using namespace clang;
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR < 15 // llvmorg-15-init-6118-gb39f43775796
|
||||||
|
namespace llvm {
|
||||||
|
template <typename T, typename E>
|
||||||
|
constexpr bool is_contained(std::initializer_list<T> set, const E &e) {
|
||||||
|
for (const T &v : set)
|
||||||
|
if (v == e)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
|
MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
|
||||||
|
|
||||||
namespace ccls {
|
namespace ccls {
|
||||||
@ -51,6 +65,10 @@ REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event);
|
|||||||
REFLECT_STRUCT(WorkspaceSymbolParam, query, folders);
|
REFLECT_STRUCT(WorkspaceSymbolParam, query, folders);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
struct Occur {
|
||||||
|
lsRange range;
|
||||||
|
Role role;
|
||||||
|
};
|
||||||
struct CclsSemanticHighlightSymbol {
|
struct CclsSemanticHighlightSymbol {
|
||||||
int id = 0;
|
int id = 0;
|
||||||
SymbolKind parentKind;
|
SymbolKind parentKind;
|
||||||
@ -58,16 +76,15 @@ struct CclsSemanticHighlightSymbol {
|
|||||||
uint8_t storage;
|
uint8_t storage;
|
||||||
std::vector<std::pair<int, int>> ranges;
|
std::vector<std::pair<int, int>> ranges;
|
||||||
|
|
||||||
// `lsRanges` is used to compute `ranges`.
|
// `lsOccur` is used to compute `ranges`.
|
||||||
std::vector<lsRange> lsRanges;
|
std::vector<Occur> lsOccurs;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CclsSemanticHighlight {
|
struct CclsSemanticHighlight {
|
||||||
DocumentUri uri;
|
DocumentUri uri;
|
||||||
std::vector<CclsSemanticHighlightSymbol> symbols;
|
std::vector<CclsSemanticHighlightSymbol> symbols;
|
||||||
};
|
};
|
||||||
REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage,
|
REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage, ranges);
|
||||||
ranges, lsRanges);
|
|
||||||
REFLECT_STRUCT(CclsSemanticHighlight, uri, symbols);
|
REFLECT_STRUCT(CclsSemanticHighlight, uri, symbols);
|
||||||
|
|
||||||
struct CclsSetSkippedRanges {
|
struct CclsSetSkippedRanges {
|
||||||
@ -76,10 +93,16 @@ struct CclsSetSkippedRanges {
|
|||||||
};
|
};
|
||||||
REFLECT_STRUCT(CclsSetSkippedRanges, uri, skippedRanges);
|
REFLECT_STRUCT(CclsSetSkippedRanges, uri, skippedRanges);
|
||||||
|
|
||||||
|
struct SemanticTokensPartialResult {
|
||||||
|
std::vector<int> data;
|
||||||
|
};
|
||||||
|
REFLECT_STRUCT(SemanticTokensPartialResult, data);
|
||||||
|
|
||||||
struct ScanLineEvent {
|
struct ScanLineEvent {
|
||||||
Position pos;
|
Position pos;
|
||||||
Position end_pos; // Second key when there is a tie for insertion events.
|
Position end_pos; // Second key when there is a tie for insertion events.
|
||||||
int id;
|
int id;
|
||||||
|
Role role;
|
||||||
CclsSemanticHighlightSymbol *symbol;
|
CclsSemanticHighlightSymbol *symbol;
|
||||||
bool operator<(const ScanLineEvent &o) const {
|
bool operator<(const ScanLineEvent &o) const {
|
||||||
// See the comments below when insertion/deletion events are inserted.
|
// See the comments below when insertion/deletion events are inserted.
|
||||||
@ -190,6 +213,8 @@ MessageHandler::MessageHandler() {
|
|||||||
bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting);
|
bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting);
|
||||||
bind("textDocument/references", &MessageHandler::textDocument_references);
|
bind("textDocument/references", &MessageHandler::textDocument_references);
|
||||||
bind("textDocument/rename", &MessageHandler::textDocument_rename);
|
bind("textDocument/rename", &MessageHandler::textDocument_rename);
|
||||||
|
bind("textDocument/semanticTokens/full", &MessageHandler::textDocument_semanticTokensFull);
|
||||||
|
bind("textDocument/semanticTokens/range", &MessageHandler::textDocument_semanticTokensRange);
|
||||||
bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp);
|
bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp);
|
||||||
bind("textDocument/typeDefinition", &MessageHandler::textDocument_typeDefinition);
|
bind("textDocument/typeDefinition", &MessageHandler::textDocument_typeDefinition);
|
||||||
bind("workspace/didChangeConfiguration", &MessageHandler::workspace_didChangeConfiguration);
|
bind("workspace/didChangeConfiguration", &MessageHandler::workspace_didChangeConfiguration);
|
||||||
@ -281,16 +306,16 @@ void emitSkippedRanges(WorkingFile *wfile, QueryFile &file) {
|
|||||||
pipeline::notify("$ccls/publishSkippedRanges", params);
|
pipeline::notify("$ccls/publishSkippedRanges", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanticTokens(DB *db, WorkingFile *wfile,
|
||||||
|
QueryFile &file) {
|
||||||
static GroupMatch match(g_config->highlight.whitelist,
|
static GroupMatch match(g_config->highlight.whitelist,
|
||||||
g_config->highlight.blacklist);
|
g_config->highlight.blacklist);
|
||||||
assert(file.def);
|
assert(file.def);
|
||||||
if (wfile->buffer_content.size() > g_config->highlight.largeFileSize ||
|
|
||||||
!match.matches(file.def->path))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Group symbols together.
|
// Group symbols together.
|
||||||
std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> grouped_symbols;
|
std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> grouped_symbols;
|
||||||
|
if (!match.matches(file.def->path))
|
||||||
|
return grouped_symbols;
|
||||||
|
|
||||||
for (auto [sym, refcnt] : file.symbol2refcnt) {
|
for (auto [sym, refcnt] : file.symbol2refcnt) {
|
||||||
if (refcnt <= 0)
|
if (refcnt <= 0)
|
||||||
continue;
|
continue;
|
||||||
@ -369,14 +394,14 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
|||||||
if (std::optional<lsRange> loc = getLsRange(wfile, sym.range)) {
|
if (std::optional<lsRange> loc = getLsRange(wfile, sym.range)) {
|
||||||
auto it = grouped_symbols.find(sym);
|
auto it = grouped_symbols.find(sym);
|
||||||
if (it != grouped_symbols.end()) {
|
if (it != grouped_symbols.end()) {
|
||||||
it->second.lsRanges.push_back(*loc);
|
it->second.lsOccurs.push_back({*loc, sym.role});
|
||||||
} else {
|
} else {
|
||||||
CclsSemanticHighlightSymbol symbol;
|
CclsSemanticHighlightSymbol symbol;
|
||||||
symbol.id = idx;
|
symbol.id = idx;
|
||||||
symbol.parentKind = parent_kind;
|
symbol.parentKind = parent_kind;
|
||||||
symbol.kind = kind;
|
symbol.kind = kind;
|
||||||
symbol.storage = storage;
|
symbol.storage = storage;
|
||||||
symbol.lsRanges.push_back(*loc);
|
symbol.lsOccurs.push_back({*loc, sym.role});
|
||||||
grouped_symbols[sym] = symbol;
|
grouped_symbols[sym] = symbol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,17 +412,17 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
|||||||
int id = 0;
|
int id = 0;
|
||||||
for (auto &entry : grouped_symbols) {
|
for (auto &entry : grouped_symbols) {
|
||||||
CclsSemanticHighlightSymbol &symbol = entry.second;
|
CclsSemanticHighlightSymbol &symbol = entry.second;
|
||||||
for (auto &loc : symbol.lsRanges) {
|
for (auto &occur : symbol.lsOccurs) {
|
||||||
// For ranges sharing the same start point, the one with leftmost end
|
// For ranges sharing the same start point, the one with leftmost end
|
||||||
// point comes first.
|
// point comes first.
|
||||||
events.push_back({loc.start, loc.end, id, &symbol});
|
events.push_back({occur.range.start, occur.range.end, id, occur.role, &symbol});
|
||||||
// For ranges sharing the same end point, their relative order does not
|
// For ranges sharing the same end point, their relative order does not
|
||||||
// matter, therefore we arbitrarily assign loc.end to them. We use
|
// matter, therefore we arbitrarily assign occur.range.end to them. We use
|
||||||
// negative id to indicate a deletion event.
|
// negative id to indicate a deletion event.
|
||||||
events.push_back({loc.end, loc.end, ~id, &symbol});
|
events.push_back({occur.range.end, occur.range.end, ~id, occur.role, &symbol});
|
||||||
id++;
|
id++;
|
||||||
}
|
}
|
||||||
symbol.lsRanges.clear();
|
symbol.lsOccurs.clear();
|
||||||
}
|
}
|
||||||
std::sort(events.begin(), events.end());
|
std::sort(events.begin(), events.end());
|
||||||
|
|
||||||
@ -413,26 +438,33 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
|||||||
// Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol
|
// Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol
|
||||||
// .
|
// .
|
||||||
if (top && !(events[i - 1].pos == events[i].pos))
|
if (top && !(events[i - 1].pos == events[i].pos))
|
||||||
events[top - 1].symbol->lsRanges.push_back(
|
events[top - 1].symbol->lsOccurs.push_back({{events[i - 1].pos, events[i].pos}, events[i].role});
|
||||||
{events[i - 1].pos, events[i].pos});
|
|
||||||
if (events[i].id >= 0)
|
if (events[i].id >= 0)
|
||||||
events[top++] = events[i];
|
events[top++] = events[i];
|
||||||
else
|
else
|
||||||
deleted[~events[i].id] = 1;
|
deleted[~events[i].id] = 1;
|
||||||
}
|
}
|
||||||
|
return grouped_symbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
||||||
|
// Disable $ccls/publishSemanticHighlight if semantic tokens support is
|
||||||
|
// enabled or the file is too large.
|
||||||
|
if (g_config->client.semanticTokensRefresh || wfile->buffer_content.size() > g_config->highlight.largeFileSize)
|
||||||
|
return;
|
||||||
|
auto grouped_symbols = computeSemanticTokens(db, wfile, file);
|
||||||
|
|
||||||
CclsSemanticHighlight params;
|
CclsSemanticHighlight params;
|
||||||
params.uri = DocumentUri::fromPath(wfile->filename);
|
params.uri = DocumentUri::fromPath(wfile->filename);
|
||||||
// Transform lsRange into pair<int, int> (offset pairs)
|
// Transform lsRange into pair<int, int> (offset pairs)
|
||||||
if (!g_config->highlight.lsRanges) {
|
{
|
||||||
std::vector<std::pair<lsRange, CclsSemanticHighlightSymbol *>> scratch;
|
std::vector<std::pair<Occur, CclsSemanticHighlightSymbol *>> scratch;
|
||||||
for (auto &entry : grouped_symbols) {
|
for (auto &entry : grouped_symbols) {
|
||||||
for (auto &range : entry.second.lsRanges)
|
for (auto &occur : entry.second.lsOccurs)
|
||||||
scratch.emplace_back(range, &entry.second);
|
scratch.push_back({occur, &entry.second});
|
||||||
entry.second.lsRanges.clear();
|
entry.second.lsOccurs.clear();
|
||||||
}
|
}
|
||||||
std::sort(scratch.begin(), scratch.end(),
|
std::sort(scratch.begin(), scratch.end(), [](auto &l, auto &r) { return l.first.range < r.first.range; });
|
||||||
[](auto &l, auto &r) { return l.first.start < r.first.start; });
|
|
||||||
const auto &buf = wfile->buffer_content;
|
const auto &buf = wfile->buffer_content;
|
||||||
int l = 0, c = 0, i = 0, p = 0;
|
int l = 0, c = 0, i = 0, p = 0;
|
||||||
auto mov = [&](int line, int col) {
|
auto mov = [&](int line, int col) {
|
||||||
@ -455,7 +487,7 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
|||||||
return c < col;
|
return c < col;
|
||||||
};
|
};
|
||||||
for (auto &entry : scratch) {
|
for (auto &entry : scratch) {
|
||||||
lsRange &r = entry.first;
|
lsRange &r = entry.first.range;
|
||||||
if (mov(r.start.line, r.start.character))
|
if (mov(r.start.line, r.start.character))
|
||||||
continue;
|
continue;
|
||||||
int beg = p;
|
int beg = p;
|
||||||
@ -466,8 +498,84 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (auto &entry : grouped_symbols)
|
for (auto &entry : grouped_symbols)
|
||||||
if (entry.second.ranges.size() || entry.second.lsRanges.size())
|
if (entry.second.ranges.size() || entry.second.lsOccurs.size())
|
||||||
params.symbols.push_back(std::move(entry.second));
|
params.symbols.push_back(std::move(entry.second));
|
||||||
pipeline::notify("$ccls/publishSemanticHighlight", params);
|
pipeline::notify("$ccls/publishSemanticHighlight", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageHandler::textDocument_semanticTokensFull(TextDocumentParam ¶m, ReplyOnce &reply) {
|
||||||
|
SemanticTokensRangeParams parameters{param.textDocument, lsRange{{0, 0}, {UINT16_MAX, INT16_MAX}}};
|
||||||
|
textDocument_semanticTokensRange(parameters, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageHandler::textDocument_semanticTokensRange(SemanticTokensRangeParams ¶m, ReplyOnce &reply) {
|
||||||
|
int file_id;
|
||||||
|
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id);
|
||||||
|
if (!wf)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto grouped_symbols = computeSemanticTokens(db, wf, *file);
|
||||||
|
std::vector<std::pair<Occur, CclsSemanticHighlightSymbol *>> scratch;
|
||||||
|
for (auto &entry : grouped_symbols) {
|
||||||
|
for (auto &occur : entry.second.lsOccurs)
|
||||||
|
scratch.emplace_back(occur, &entry.second);
|
||||||
|
entry.second.lsOccurs.clear();
|
||||||
|
}
|
||||||
|
std::sort(scratch.begin(), scratch.end(), [](auto &l, auto &r) { return l.first.range < r.first.range; });
|
||||||
|
|
||||||
|
SemanticTokensPartialResult result;
|
||||||
|
int line = 0, column = 0;
|
||||||
|
for (auto &entry : scratch) {
|
||||||
|
lsRange &r = entry.first.range;
|
||||||
|
CclsSemanticHighlightSymbol &symbol = *entry.second;
|
||||||
|
if (r.start.line != line)
|
||||||
|
column = 0;
|
||||||
|
result.data.push_back(r.start.line - line);
|
||||||
|
line = r.start.line;
|
||||||
|
result.data.push_back(r.start.character - column);
|
||||||
|
column = r.start.character;
|
||||||
|
result.data.push_back(r.end.character - r.start.character);
|
||||||
|
|
||||||
|
int tokenType = (int)symbol.kind, modifier = 0;
|
||||||
|
if (tokenType == (int)SymbolKind::StaticMethod) {
|
||||||
|
tokenType = (int)SymbolKind::Method;
|
||||||
|
modifier |= 1 << (int)TokenModifier::Static;
|
||||||
|
} else if (tokenType >= (int)SymbolKind::FirstExtension) {
|
||||||
|
tokenType += (int)SymbolKind::FirstNonStandard - (int)SymbolKind::FirstExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set modifiers.
|
||||||
|
if (entry.first.role & Role::Declaration)
|
||||||
|
modifier |= 1 << (int)TokenModifier::Declaration;
|
||||||
|
if (entry.first.role & Role::Definition)
|
||||||
|
modifier |= 1 << (int)TokenModifier::Definition;
|
||||||
|
if (entry.first.role & Role::Read)
|
||||||
|
modifier |= 1 << (int)TokenModifier::Read;
|
||||||
|
if (entry.first.role & Role::Write)
|
||||||
|
modifier |= 1 << (int)TokenModifier::Write;
|
||||||
|
if (symbol.storage == SC_Static)
|
||||||
|
modifier |= 1 << (int)TokenModifier::Static;
|
||||||
|
|
||||||
|
if (llvm::is_contained({SymbolKind::Constructor, SymbolKind::Field, SymbolKind::Method, SymbolKind::StaticMethod},
|
||||||
|
symbol.kind))
|
||||||
|
modifier |= 1 << (int)TokenModifier::ClassScope;
|
||||||
|
else if (llvm::is_contained({SymbolKind::File, SymbolKind::Namespace}, symbol.parentKind))
|
||||||
|
modifier |= 1 << (int)TokenModifier::NamespaceScope;
|
||||||
|
else if (llvm::is_contained(
|
||||||
|
{SymbolKind::Constructor, SymbolKind::Function, SymbolKind::Method, SymbolKind::StaticMethod},
|
||||||
|
symbol.parentKind))
|
||||||
|
modifier |= 1 << (int)TokenModifier::FunctionScope;
|
||||||
|
|
||||||
|
// Rainbow semantic tokens
|
||||||
|
static_assert((int)TokenModifier::Id0 + 20 < 31);
|
||||||
|
if (int rainbow = g_config->highlight.rainbow)
|
||||||
|
modifier |= 1 << ((int)TokenModifier::Id0 + symbol.id % std::min(rainbow, 20));
|
||||||
|
|
||||||
|
result.data.push_back(tokenType);
|
||||||
|
result.data.push_back(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
reply(result);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ccls
|
} // namespace ccls
|
||||||
|
@ -40,6 +40,11 @@ struct RenameParam {
|
|||||||
Position position;
|
Position position;
|
||||||
std::string newName;
|
std::string newName;
|
||||||
};
|
};
|
||||||
|
struct SemanticTokensRangeParams {
|
||||||
|
TextDocumentIdentifier textDocument;
|
||||||
|
lsRange range;
|
||||||
|
};
|
||||||
|
REFLECT_STRUCT(SemanticTokensRangeParams, textDocument, range);
|
||||||
struct TextDocumentParam {
|
struct TextDocumentParam {
|
||||||
TextDocumentIdentifier textDocument;
|
TextDocumentIdentifier textDocument;
|
||||||
};
|
};
|
||||||
@ -303,6 +308,8 @@ private:
|
|||||||
ReplyOnce &);
|
ReplyOnce &);
|
||||||
void textDocument_references(JsonReader &, ReplyOnce &);
|
void textDocument_references(JsonReader &, ReplyOnce &);
|
||||||
void textDocument_rename(RenameParam &, ReplyOnce &);
|
void textDocument_rename(RenameParam &, ReplyOnce &);
|
||||||
|
void textDocument_semanticTokensFull(TextDocumentParam &, ReplyOnce &);
|
||||||
|
void textDocument_semanticTokensRange(SemanticTokensRangeParams &, ReplyOnce &);
|
||||||
void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &);
|
void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &);
|
||||||
void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &);
|
void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &);
|
||||||
void workspace_didChangeConfiguration(EmptyParam &);
|
void workspace_didChangeConfiguration(EmptyParam &);
|
||||||
|
@ -24,6 +24,51 @@
|
|||||||
namespace ccls {
|
namespace ccls {
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
|
const char *const kTokenTypes[] = {
|
||||||
|
"unknown",
|
||||||
|
|
||||||
|
"file",
|
||||||
|
"module",
|
||||||
|
"namespace",
|
||||||
|
"package",
|
||||||
|
"class",
|
||||||
|
"method",
|
||||||
|
"property",
|
||||||
|
"field",
|
||||||
|
"constructor",
|
||||||
|
"enum",
|
||||||
|
"interface",
|
||||||
|
"function",
|
||||||
|
"variable",
|
||||||
|
"constant",
|
||||||
|
"string",
|
||||||
|
"number",
|
||||||
|
"boolean",
|
||||||
|
"array",
|
||||||
|
"object",
|
||||||
|
"key",
|
||||||
|
"null",
|
||||||
|
"enumMember",
|
||||||
|
"struct",
|
||||||
|
"event",
|
||||||
|
"operator",
|
||||||
|
"typeParameter",
|
||||||
|
|
||||||
|
// 252 => 27
|
||||||
|
"typeAlias",
|
||||||
|
"parameter",
|
||||||
|
"staticMethod",
|
||||||
|
"macro",
|
||||||
|
};
|
||||||
|
static_assert(std::size(kTokenTypes) ==
|
||||||
|
int(SymbolKind::FirstNonStandard) + int(SymbolKind::LastExtension) - int(SymbolKind::FirstExtension) + 1);
|
||||||
|
|
||||||
|
const char *const kTokenModifiers[] = {
|
||||||
|
#define TOKEN_MODIFIER(name, str) str,
|
||||||
|
#include "enum.inc"
|
||||||
|
#undef TOKEN_MODIFIER
|
||||||
|
};
|
||||||
|
|
||||||
extern std::vector<std::string> g_init_options;
|
extern std::vector<std::string> g_init_options;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -88,6 +133,14 @@ struct ServerCap {
|
|||||||
std::vector<const char *> commands = {ccls_xref};
|
std::vector<const char *> commands = {ccls_xref};
|
||||||
} executeCommandProvider;
|
} executeCommandProvider;
|
||||||
bool callHierarchyProvider = true;
|
bool callHierarchyProvider = true;
|
||||||
|
struct SemanticTokenProvider {
|
||||||
|
struct SemanticTokensLegend {
|
||||||
|
std::vector<const char *> tokenTypes{std::begin(kTokenTypes), std::end(kTokenTypes)};
|
||||||
|
std::vector<const char *> tokenModifiers{std::begin(kTokenModifiers), std::end(kTokenModifiers)};
|
||||||
|
} legend;
|
||||||
|
bool range = true;
|
||||||
|
bool full = true;
|
||||||
|
} semanticTokensProvider;
|
||||||
Config::ServerCap::Workspace workspace;
|
Config::ServerCap::Workspace workspace;
|
||||||
};
|
};
|
||||||
REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds);
|
REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds);
|
||||||
@ -100,16 +153,14 @@ REFLECT_STRUCT(ServerCap::SaveOptions, includeText);
|
|||||||
REFLECT_STRUCT(ServerCap::SignatureHelpOptions, triggerCharacters);
|
REFLECT_STRUCT(ServerCap::SignatureHelpOptions, triggerCharacters);
|
||||||
REFLECT_STRUCT(ServerCap::TextDocumentSyncOptions, openClose, change, willSave,
|
REFLECT_STRUCT(ServerCap::TextDocumentSyncOptions, openClose, change, willSave,
|
||||||
willSaveWaitUntil, save);
|
willSaveWaitUntil, save);
|
||||||
REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider,
|
REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider, signatureHelpProvider,
|
||||||
signatureHelpProvider, declarationProvider, definitionProvider,
|
declarationProvider, definitionProvider, implementationProvider, typeDefinitionProvider,
|
||||||
implementationProvider, typeDefinitionProvider,
|
referencesProvider, documentHighlightProvider, documentSymbolProvider, workspaceSymbolProvider,
|
||||||
referencesProvider, documentHighlightProvider,
|
codeActionProvider, codeLensProvider, documentFormattingProvider, documentRangeFormattingProvider,
|
||||||
documentSymbolProvider, workspaceSymbolProvider,
|
documentOnTypeFormattingProvider, renameProvider, documentLinkProvider, foldingRangeProvider,
|
||||||
codeActionProvider, codeLensProvider, documentFormattingProvider,
|
executeCommandProvider, callHierarchyProvider, semanticTokensProvider, workspace);
|
||||||
documentRangeFormattingProvider,
|
REFLECT_STRUCT(ServerCap::SemanticTokenProvider, legend, range, full);
|
||||||
documentOnTypeFormattingProvider, renameProvider,
|
REFLECT_STRUCT(ServerCap::SemanticTokenProvider::SemanticTokensLegend, tokenTypes, tokenModifiers);
|
||||||
documentLinkProvider, foldingRangeProvider,
|
|
||||||
executeCommandProvider, callHierarchyProvider, workspace);
|
|
||||||
|
|
||||||
struct DynamicReg {
|
struct DynamicReg {
|
||||||
bool dynamicRegistration = false;
|
bool dynamicRegistration = false;
|
||||||
@ -132,12 +183,16 @@ struct WorkspaceClientCap {
|
|||||||
DynamicReg didChangeWatchedFiles;
|
DynamicReg didChangeWatchedFiles;
|
||||||
DynamicReg symbol;
|
DynamicReg symbol;
|
||||||
DynamicReg executeCommand;
|
DynamicReg executeCommand;
|
||||||
|
|
||||||
|
struct SemanticTokensWorkspace {
|
||||||
|
bool refreshSupport = false;
|
||||||
|
} semanticTokens;
|
||||||
};
|
};
|
||||||
|
|
||||||
REFLECT_STRUCT(WorkspaceClientCap::WorkspaceEdit, documentChanges);
|
REFLECT_STRUCT(WorkspaceClientCap::WorkspaceEdit, documentChanges);
|
||||||
REFLECT_STRUCT(WorkspaceClientCap, applyEdit, workspaceEdit,
|
REFLECT_STRUCT(WorkspaceClientCap::SemanticTokensWorkspace, refreshSupport);
|
||||||
didChangeConfiguration, didChangeWatchedFiles, symbol,
|
REFLECT_STRUCT(WorkspaceClientCap, applyEdit, workspaceEdit, didChangeConfiguration, didChangeWatchedFiles, symbol,
|
||||||
executeCommand);
|
executeCommand, semanticTokens);
|
||||||
|
|
||||||
// Text document specific client capabilities.
|
// Text document specific client capabilities.
|
||||||
struct TextDocumentClientCap {
|
struct TextDocumentClientCap {
|
||||||
@ -319,6 +374,7 @@ void do_initialize(MessageHandler *m, InitializeParam ¶m,
|
|||||||
capabilities.textDocument.publishDiagnostics.relatedInformation;
|
capabilities.textDocument.publishDiagnostics.relatedInformation;
|
||||||
didChangeWatchedFiles =
|
didChangeWatchedFiles =
|
||||||
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
|
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
|
||||||
|
g_config->client.semanticTokensRefresh &= capabilities.workspace.semanticTokens.refreshSupport;
|
||||||
|
|
||||||
if (!g_config->client.snippetSupport)
|
if (!g_config->client.snippetSupport)
|
||||||
g_config->completion.duplicateOptional = false;
|
g_config->completion.duplicateOptional = false;
|
||||||
|
@ -517,6 +517,10 @@ void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) {
|
|||||||
QueryFile &file = db->files[db->name2file_id[path]];
|
QueryFile &file = db->files[db->name2file_id[path]];
|
||||||
emitSemanticHighlight(db, wf.get(), file);
|
emitSemanticHighlight(db, wf.get(), file);
|
||||||
}
|
}
|
||||||
|
if (g_config->client.semanticTokensRefresh) {
|
||||||
|
std::optional<bool> param;
|
||||||
|
request("workspace/semanticTokens/refresh", param);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,6 +537,12 @@ void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) {
|
|||||||
QueryFile &file = db->files[update->file_id];
|
QueryFile &file = db->files[update->file_id];
|
||||||
emitSkippedRanges(wfile, file);
|
emitSkippedRanges(wfile, file);
|
||||||
emitSemanticHighlight(db, wfile, file);
|
emitSemanticHighlight(db, wfile, file);
|
||||||
|
if (g_config->client.semanticTokensRefresh) {
|
||||||
|
// Return filename, even if the spec indicates params is none.
|
||||||
|
TextDocumentIdentifier param;
|
||||||
|
param.uri = DocumentUri::fromPath(wfile->filename);
|
||||||
|
request("workspace/semanticTokens/refresh", param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user