1111#include < rapidjson/document.h>
1212#include < rapidjson/reader.h>
1313
14+ #include < llvm/ADT/STLExtras.h>
15+
1416#include < algorithm>
1517#include < stdexcept>
1618
1719using namespace clang ;
1820
21+ #if LLVM_VERSION_MAJOR < 15 // llvmorg-15-init-6118-gb39f43775796
22+ namespace llvm {
23+ template <typename T, typename E>
24+ constexpr bool is_contained (std::initializer_list<T> set, const E &e) {
25+ for (const T &v : set)
26+ if (v == e)
27+ return true ;
28+ return false ;
29+ }
30+ }
31+ #endif
32+
1933MAKE_HASHABLE (ccls::SymbolIdx, t.usr, t.kind);
2034
2135namespace ccls {
@@ -51,23 +65,26 @@ REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event);
5165REFLECT_STRUCT (WorkspaceSymbolParam, query, folders);
5266
5367namespace {
68+ struct Occur {
69+ lsRange range;
70+ Role role;
71+ };
5472struct CclsSemanticHighlightSymbol {
5573 int id = 0 ;
5674 SymbolKind parentKind;
5775 SymbolKind kind;
5876 uint8_t storage;
5977 std::vector<std::pair<int , int >> ranges;
6078
61- // `lsRanges ` is used to compute `ranges`.
62- std::vector<lsRange> lsRanges ;
79+ // `lsOccur ` is used to compute `ranges`.
80+ std::vector<Occur> lsOccurs ;
6381};
6482
6583struct CclsSemanticHighlight {
6684 DocumentUri uri;
6785 std::vector<CclsSemanticHighlightSymbol> symbols;
6886};
69- REFLECT_STRUCT (CclsSemanticHighlightSymbol, id, parentKind, kind, storage,
70- ranges, lsRanges);
87+ REFLECT_STRUCT (CclsSemanticHighlightSymbol, id, parentKind, kind, storage, ranges);
7188REFLECT_STRUCT (CclsSemanticHighlight, uri, symbols);
7289
7390struct CclsSetSkippedRanges {
@@ -76,10 +93,16 @@ struct CclsSetSkippedRanges {
7693};
7794REFLECT_STRUCT (CclsSetSkippedRanges, uri, skippedRanges);
7895
96+ struct SemanticTokensPartialResult {
97+ std::vector<int > data;
98+ };
99+ REFLECT_STRUCT (SemanticTokensPartialResult, data);
100+
79101struct ScanLineEvent {
80102 Position pos;
81103 Position end_pos; // Second key when there is a tie for insertion events.
82104 int id;
105+ Role role;
83106 CclsSemanticHighlightSymbol *symbol;
84107 bool operator <(const ScanLineEvent &o) const {
85108 // See the comments below when insertion/deletion events are inserted.
@@ -190,6 +213,8 @@ MessageHandler::MessageHandler() {
190213 bind (" textDocument/rangeFormatting" , &MessageHandler::textDocument_rangeFormatting);
191214 bind (" textDocument/references" , &MessageHandler::textDocument_references);
192215 bind (" textDocument/rename" , &MessageHandler::textDocument_rename);
216+ bind (" textDocument/semanticTokens/full" , &MessageHandler::textDocument_semanticTokensFull);
217+ bind (" textDocument/semanticTokens/range" , &MessageHandler::textDocument_semanticTokensRange);
193218 bind (" textDocument/signatureHelp" , &MessageHandler::textDocument_signatureHelp);
194219 bind (" textDocument/typeDefinition" , &MessageHandler::textDocument_typeDefinition);
195220 bind (" workspace/didChangeConfiguration" , &MessageHandler::workspace_didChangeConfiguration);
@@ -281,16 +306,16 @@ void emitSkippedRanges(WorkingFile *wfile, QueryFile &file) {
281306 pipeline::notify (" $ccls/publishSkippedRanges" , params);
282307}
283308
284- void emitSemanticHighlight (DB *db, WorkingFile *wfile, QueryFile &file) {
309+ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanticTokens (DB *db, WorkingFile *wfile,
310+ QueryFile &file) {
285311 static GroupMatch match (g_config->highlight .whitelist ,
286312 g_config->highlight .blacklist );
287313 assert (file.def );
288- if (wfile->buffer_content .size () > g_config->highlight .largeFileSize ||
289- !match.matches (file.def ->path ))
290- return ;
291-
292314 // Group symbols together.
293315 std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> grouped_symbols;
316+ if (!match.matches (file.def ->path ))
317+ return grouped_symbols;
318+
294319 for (auto [sym, refcnt] : file.symbol2refcnt ) {
295320 if (refcnt <= 0 )
296321 continue ;
@@ -369,14 +394,14 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
369394 if (std::optional<lsRange> loc = getLsRange (wfile, sym.range )) {
370395 auto it = grouped_symbols.find (sym);
371396 if (it != grouped_symbols.end ()) {
372- it->second .lsRanges .push_back (*loc);
397+ it->second .lsOccurs .push_back ({ *loc, sym. role } );
373398 } else {
374399 CclsSemanticHighlightSymbol symbol;
375400 symbol.id = idx;
376401 symbol.parentKind = parent_kind;
377402 symbol.kind = kind;
378403 symbol.storage = storage;
379- symbol.lsRanges .push_back (*loc);
404+ symbol.lsOccurs .push_back ({ *loc, sym. role } );
380405 grouped_symbols[sym] = symbol;
381406 }
382407 }
@@ -387,17 +412,17 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
387412 int id = 0 ;
388413 for (auto &entry : grouped_symbols) {
389414 CclsSemanticHighlightSymbol &symbol = entry.second ;
390- for (auto &loc : symbol.lsRanges ) {
415+ for (auto &occur : symbol.lsOccurs ) {
391416 // For ranges sharing the same start point, the one with leftmost end
392417 // point comes first.
393- events.push_back ({loc. start , loc. end , id, &symbol});
418+ events.push_back ({occur. range . start , occur. range . end , id, occur. role , &symbol});
394419 // For ranges sharing the same end point, their relative order does not
395- // matter, therefore we arbitrarily assign loc .end to them. We use
420+ // matter, therefore we arbitrarily assign occur.range .end to them. We use
396421 // negative id to indicate a deletion event.
397- events.push_back ({loc. end , loc. end , ~id, &symbol});
422+ events.push_back ({occur. range . end , occur. range . end , ~id, occur. role , &symbol});
398423 id++;
399424 }
400- symbol.lsRanges .clear ();
425+ symbol.lsOccurs .clear ();
401426 }
402427 std::sort (events.begin (), events.end ());
403428
@@ -413,26 +438,33 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
413438 // Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol
414439 // .
415440 if (top && !(events[i - 1 ].pos == events[i].pos ))
416- events[top - 1 ].symbol ->lsRanges .push_back (
417- {events[i - 1 ].pos , events[i].pos });
441+ events[top - 1 ].symbol ->lsOccurs .push_back ({{events[i - 1 ].pos , events[i].pos }, events[i].role });
418442 if (events[i].id >= 0 )
419443 events[top++] = events[i];
420444 else
421445 deleted[~events[i].id ] = 1 ;
422446 }
447+ return grouped_symbols;
448+ }
449+
450+ void emitSemanticHighlight (DB *db, WorkingFile *wfile, QueryFile &file) {
451+ // Disable $ccls/publishSemanticHighlight if semantic tokens support is
452+ // enabled or the file is too large.
453+ if (g_config->client .semanticTokensRefresh || wfile->buffer_content .size () > g_config->highlight .largeFileSize )
454+ return ;
455+ auto grouped_symbols = computeSemanticTokens (db, wfile, file);
423456
424457 CclsSemanticHighlight params;
425458 params.uri = DocumentUri::fromPath (wfile->filename );
426459 // Transform lsRange into pair<int, int> (offset pairs)
427- if (!g_config-> highlight . lsRanges ) {
428- std::vector<std::pair<lsRange , CclsSemanticHighlightSymbol *>> scratch;
460+ {
461+ std::vector<std::pair<Occur , CclsSemanticHighlightSymbol *>> scratch;
429462 for (auto &entry : grouped_symbols) {
430- for (auto &range : entry.second .lsRanges )
431- scratch.emplace_back (range , &entry.second );
432- entry.second .lsRanges .clear ();
463+ for (auto &occur : entry.second .lsOccurs )
464+ scratch.push_back ({occur , &entry.second } );
465+ entry.second .lsOccurs .clear ();
433466 }
434- std::sort (scratch.begin (), scratch.end (),
435- [](auto &l, auto &r) { return l.first .start < r.first .start ; });
467+ std::sort (scratch.begin (), scratch.end (), [](auto &l, auto &r) { return l.first .range < r.first .range ; });
436468 const auto &buf = wfile->buffer_content ;
437469 int l = 0 , c = 0 , i = 0 , p = 0 ;
438470 auto mov = [&](int line, int col) {
@@ -455,7 +487,7 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
455487 return c < col;
456488 };
457489 for (auto &entry : scratch) {
458- lsRange &r = entry.first ;
490+ lsRange &r = entry.first . range ;
459491 if (mov (r.start .line , r.start .character ))
460492 continue ;
461493 int beg = p;
@@ -466,8 +498,84 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
466498 }
467499
468500 for (auto &entry : grouped_symbols)
469- if (entry.second .ranges .size () || entry.second .lsRanges .size ())
501+ if (entry.second .ranges .size () || entry.second .lsOccurs .size ())
470502 params.symbols .push_back (std::move (entry.second ));
471503 pipeline::notify (" $ccls/publishSemanticHighlight" , params);
472504}
505+
506+ void MessageHandler::textDocument_semanticTokensFull (TextDocumentParam ¶m, ReplyOnce &reply) {
507+ SemanticTokensRangeParams parameters{param.textDocument , lsRange{{0 , 0 }, {UINT16_MAX, INT16_MAX}}};
508+ textDocument_semanticTokensRange (parameters, reply);
509+ }
510+
511+ void MessageHandler::textDocument_semanticTokensRange (SemanticTokensRangeParams ¶m, ReplyOnce &reply) {
512+ int file_id;
513+ auto [file, wf] = findOrFail (param.textDocument .uri .getPath (), reply, &file_id);
514+ if (!wf)
515+ return ;
516+
517+ auto grouped_symbols = computeSemanticTokens (db, wf, *file);
518+ std::vector<std::pair<Occur, CclsSemanticHighlightSymbol *>> scratch;
519+ for (auto &entry : grouped_symbols) {
520+ for (auto &occur : entry.second .lsOccurs )
521+ scratch.emplace_back (occur, &entry.second );
522+ entry.second .lsOccurs .clear ();
523+ }
524+ std::sort (scratch.begin (), scratch.end (), [](auto &l, auto &r) { return l.first .range < r.first .range ; });
525+
526+ SemanticTokensPartialResult result;
527+ int line = 0 , column = 0 ;
528+ for (auto &entry : scratch) {
529+ lsRange &r = entry.first .range ;
530+ CclsSemanticHighlightSymbol &symbol = *entry.second ;
531+ if (r.start .line != line)
532+ column = 0 ;
533+ result.data .push_back (r.start .line - line);
534+ line = r.start .line ;
535+ result.data .push_back (r.start .character - column);
536+ column = r.start .character ;
537+ result.data .push_back (r.end .character - r.start .character );
538+
539+ int tokenType = (int )symbol.kind , modifier = 0 ;
540+ if (tokenType == (int )SymbolKind::StaticMethod) {
541+ tokenType = (int )SymbolKind::Method;
542+ modifier |= 1 << (int )TokenModifier::Static;
543+ } else if (tokenType >= (int )SymbolKind::FirstExtension) {
544+ tokenType += (int )SymbolKind::FirstNonStandard - (int )SymbolKind::FirstExtension;
545+ }
546+
547+ // Set modifiers.
548+ if (entry.first .role & Role::Declaration)
549+ modifier |= 1 << (int )TokenModifier::Declaration;
550+ if (entry.first .role & Role::Definition)
551+ modifier |= 1 << (int )TokenModifier::Definition;
552+ if (entry.first .role & Role::Read)
553+ modifier |= 1 << (int )TokenModifier::Read;
554+ if (entry.first .role & Role::Write)
555+ modifier |= 1 << (int )TokenModifier::Write;
556+ if (symbol.storage == SC_Static)
557+ modifier |= 1 << (int )TokenModifier::Static;
558+
559+ if (llvm::is_contained ({SymbolKind::Constructor, SymbolKind::Field, SymbolKind::Method, SymbolKind::StaticMethod},
560+ symbol.kind ))
561+ modifier |= 1 << (int )TokenModifier::ClassScope;
562+ else if (llvm::is_contained ({SymbolKind::File, SymbolKind::Namespace}, symbol.parentKind ))
563+ modifier |= 1 << (int )TokenModifier::NamespaceScope;
564+ else if (llvm::is_contained (
565+ {SymbolKind::Constructor, SymbolKind::Function, SymbolKind::Method, SymbolKind::StaticMethod},
566+ symbol.parentKind ))
567+ modifier |= 1 << (int )TokenModifier::FunctionScope;
568+
569+ // Rainbow semantic tokens
570+ static_assert ((int )TokenModifier::Id0 + 20 < 31 );
571+ if (int rainbow = g_config->highlight .rainbow )
572+ modifier |= 1 << ((int )TokenModifier::Id0 + symbol.id % std::min (rainbow, 20 ));
573+
574+ result.data .push_back (tokenType);
575+ result.data .push_back (modifier);
576+ }
577+
578+ reply (result);
579+ }
580+
473581} // namespace ccls
0 commit comments