diff --git a/CMakeLists.txt b/CMakeLists.txt index b3a02386b..788977976 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -235,6 +235,7 @@ target_sources(ccls PRIVATE src/messages/textDocument_hover.cc src/messages/textDocument_references.cc src/messages/textDocument_rename.cc + src/messages/textDocument_semanticToken.cc src/messages/textDocument_signatureHelp.cc src/messages/workspace.cc ) diff --git a/src/message_handler.cc b/src/message_handler.cc index 498a72deb..151214604 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -50,24 +50,6 @@ REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event); REFLECT_STRUCT(WorkspaceSymbolParam, query, folders); namespace { -struct CclsSemanticHighlightSymbol { - int id = 0; - SymbolKind parentKind; - SymbolKind kind; - uint8_t storage; - std::vector> ranges; - - // `lsRanges` is used to compute `ranges`. - std::vector lsRanges; -}; - -struct CclsSemanticHighlight { - DocumentUri uri; - std::vector symbols; -}; -REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage, - ranges, lsRanges); -REFLECT_STRUCT(CclsSemanticHighlight, uri, symbols); struct CclsSetSkippedRanges { DocumentUri uri; @@ -75,26 +57,6 @@ struct CclsSetSkippedRanges { }; REFLECT_STRUCT(CclsSetSkippedRanges, uri, skippedRanges); -struct ScanLineEvent { - Position pos; - Position end_pos; // Second key when there is a tie for insertion events. - int id; - CclsSemanticHighlightSymbol *symbol; - bool operator<(const ScanLineEvent &o) const { - // See the comments below when insertion/deletion events are inserted. - if (!(pos == o.pos)) - return pos < o.pos; - if (!(o.end_pos == end_pos)) - return o.end_pos < end_pos; - // This comparison essentially order Macro after non-Macro, - // So that macros will not be rendered as Var/Type/... - if (symbol->kind != o.symbol->kind) - return symbol->kind < o.symbol->kind; - // If symbol A and B occupy the same place, we want one to be placed - // before the other consistantly. - return symbol->id < o.symbol->id; - } -}; } // namespace void ReplyOnce::notOpened(std::string_view path) { @@ -188,6 +150,8 @@ MessageHandler::MessageHandler() { bind("textDocument/rename", &MessageHandler::textDocument_rename); bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp); bind("textDocument/typeDefinition", &MessageHandler::textDocument_typeDefinition); + bind("textDocument/semanticTokens/full", &MessageHandler::textDocument_semanticTokensFull); + bind("textDocument/semanticTokens/range", &MessageHandler::textDocument_semanticTokensRange); bind("workspace/didChangeConfiguration", &MessageHandler::workspace_didChangeConfiguration); bind("workspace/didChangeWatchedFiles", &MessageHandler::workspace_didChangeWatchedFiles); bind("workspace/didChangeWorkspaceFolders", &MessageHandler::workspace_didChangeWorkspaceFolders); @@ -277,193 +241,9 @@ void emitSkippedRanges(WorkingFile *wfile, QueryFile &file) { pipeline::notify("$ccls/publishSkippedRanges", params); } -void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) { - static GroupMatch match(g_config->highlight.whitelist, - g_config->highlight.blacklist); - assert(file.def); - if (wfile->buffer_content.size() > g_config->highlight.largeFileSize || - !match.matches(file.def->path)) - return; - - // Group symbols together. - std::unordered_map grouped_symbols; - for (auto [sym, refcnt] : file.symbol2refcnt) { - if (refcnt <= 0) - continue; - std::string_view detailed_name; - SymbolKind parent_kind = SymbolKind::Unknown; - SymbolKind kind = SymbolKind::Unknown; - uint8_t storage = SC_None; - int idx; - // This switch statement also filters out symbols that are not highlighted. - switch (sym.kind) { - case Kind::Func: { - idx = db->func_usr[sym.usr]; - const QueryFunc &func = db->funcs[idx]; - const QueryFunc::Def *def = func.anyDef(); - if (!def) - continue; // applies to for loop - // Don't highlight overloadable operators or implicit lambda -> - // std::function constructor. - std::string_view short_name = def->name(false); - if (short_name.compare(0, 8, "operator") == 0) - continue; // applies to for loop - kind = def->kind; - storage = def->storage; - detailed_name = short_name; - parent_kind = def->parent_kind; - - // Check whether the function name is actually there. - // If not, do not publish the semantic highlight. - // E.g. copy-initialization of constructors should not be highlighted - // but we still want to keep the range for jumping to definition. - std::string_view concise_name = - detailed_name.substr(0, detailed_name.find('<')); - uint16_t start_line = sym.range.start.line; - int16_t start_col = sym.range.start.column; - if (start_line >= wfile->index_lines.size()) - continue; - std::string_view line = wfile->index_lines[start_line]; - sym.range.end.line = start_line; - if (!(start_col + concise_name.size() <= line.size() && - line.compare(start_col, concise_name.size(), concise_name) == 0)) - continue; - sym.range.end.column = start_col + concise_name.size(); - break; - } - case Kind::Type: { - idx = db->type_usr[sym.usr]; - const QueryType &type = db->types[idx]; - for (auto &def : type.def) { - kind = def.kind; - detailed_name = def.detailed_name; - if (def.spell) { - parent_kind = def.parent_kind; - break; - } - } - break; - } - case Kind::Var: { - idx = db->var_usr[sym.usr]; - const QueryVar &var = db->vars[idx]; - for (auto &def : var.def) { - kind = def.kind; - storage = def.storage; - detailed_name = def.detailed_name; - if (def.spell) { - parent_kind = def.parent_kind; - break; - } - } - break; - } - default: - continue; // applies to for loop - } - - if (std::optional loc = getLsRange(wfile, sym.range)) { - auto it = grouped_symbols.find(sym); - if (it != grouped_symbols.end()) { - it->second.lsRanges.push_back(*loc); - } else { - CclsSemanticHighlightSymbol symbol; - symbol.id = idx; - symbol.parentKind = parent_kind; - symbol.kind = kind; - symbol.storage = storage; - symbol.lsRanges.push_back(*loc); - grouped_symbols[sym] = symbol; - } - } - } - - // Make ranges non-overlapping using a scan line algorithm. - std::vector events; - int id = 0; - for (auto &entry : grouped_symbols) { - CclsSemanticHighlightSymbol &symbol = entry.second; - for (auto &loc : symbol.lsRanges) { - // For ranges sharing the same start point, the one with leftmost end - // point comes first. - events.push_back({loc.start, loc.end, id, &symbol}); - // For ranges sharing the same end point, their relative order does not - // matter, therefore we arbitrarily assign loc.end to them. We use - // negative id to indicate a deletion event. - events.push_back({loc.end, loc.end, ~id, &symbol}); - id++; - } - symbol.lsRanges.clear(); - } - std::sort(events.begin(), events.end()); - - std::vector deleted(id, 0); - int top = 0; - for (size_t i = 0; i < events.size(); i++) { - while (top && deleted[events[top - 1].id]) - top--; - // Order [a, b0) after [a, b1) if b0 < b1. The range comes later overrides - // the ealier. The order of [a0, b) [a1, b) does not matter. - // The order of [a, b) [b, c) does not as long as we do not emit empty - // ranges. - // Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol - // . - if (top && !(events[i - 1].pos == events[i].pos)) - events[top - 1].symbol->lsRanges.push_back( - {events[i - 1].pos, events[i].pos}); - if (events[i].id >= 0) - events[top++] = events[i]; - else - deleted[~events[i].id] = 1; - } - - CclsSemanticHighlight params; - params.uri = DocumentUri::fromPath(wfile->filename); - // Transform lsRange into pair (offset pairs) - if (!g_config->highlight.lsRanges) { - std::vector> scratch; - for (auto &entry : grouped_symbols) { - for (auto &range : entry.second.lsRanges) - scratch.emplace_back(range, &entry.second); - entry.second.lsRanges.clear(); - } - std::sort(scratch.begin(), scratch.end(), - [](auto &l, auto &r) { return l.first.start < r.first.start; }); - const auto &buf = wfile->buffer_content; - int l = 0, c = 0, i = 0, p = 0; - auto mov = [&](int line, int col) { - if (l < line) - c = 0; - for (; l < line && i < buf.size(); i++) { - if (buf[i] == '\n') - l++; - if (uint8_t(buf[i]) < 128 || 192 <= uint8_t(buf[i])) - p++; - } - if (l < line) - return true; - for (; c < col && i < buf.size() && buf[i] != '\n'; c++) - if (p++, uint8_t(buf[i++]) >= 128) - // Skip 0b10xxxxxx - while (i < buf.size() && uint8_t(buf[i]) >= 128 && - uint8_t(buf[i]) < 192) - i++; - return c < col; - }; - for (auto &entry : scratch) { - lsRange &r = entry.first; - if (mov(r.start.line, r.start.character)) - continue; - int beg = p; - if (mov(r.end.line, r.end.character)) - continue; - entry.second->ranges.emplace_back(beg, p); - } - } - for (auto &entry : grouped_symbols) - if (entry.second.ranges.size() || entry.second.lsRanges.size()) - params.symbols.push_back(std::move(entry.second)); - pipeline::notify("$ccls/publishSemanticHighlight", params); +void emitSemanticHighlightRefresh() { + std::vector emptyParameters{}; // notification with no parameters (empty list) + pipeline::notify("workspace/semanticTokens/refresh", emptyParameters); } } // namespace ccls diff --git a/src/message_handler.hh b/src/message_handler.hh index 7718e3467..b21842fe0 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -48,6 +48,15 @@ struct TextDocumentPositionParam { TextDocumentIdentifier textDocument; Position position; }; +struct SemanticTokensParams { + TextDocumentIdentifier textDocument; +}; +REFLECT_STRUCT(SemanticTokensParams, textDocument); +struct SemanticTokensRangeParams { + TextDocumentIdentifier textDocument; + lsRange range; +}; +REFLECT_STRUCT(SemanticTokensRangeParams, textDocument, range); struct TextDocumentEdit { VersionedTextDocumentIdentifier textDocument; std::vector edits; @@ -287,6 +296,8 @@ private: void textDocument_rename(RenameParam &, ReplyOnce &); void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &); void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_semanticTokensFull(SemanticTokensParams &, ReplyOnce &); + void textDocument_semanticTokensRange(SemanticTokensRangeParams &, ReplyOnce &); void workspace_didChangeConfiguration(EmptyParam &); void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &); void workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam &); @@ -296,5 +307,6 @@ private: void emitSkippedRanges(WorkingFile *wfile, QueryFile &file); -void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file); +//! Tell client there should be a highlighting refresh +void emitSemanticHighlightRefresh(); } // namespace ccls diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 1601db704..c719b3452 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -1,4 +1,4 @@ -// Copyright 2017-2018 ccls Authors +// Copyright ccls Authors // SPDX-License-Identifier: Apache-2.0 #include "filesystem.hh" @@ -25,6 +25,47 @@ namespace ccls { using namespace llvm; +const char * SEMANTIC_TOKENS[] = { + "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", + "typeAlias", //252 => 27 + "parameter", + "staticMethod", + "macro" +}; + +const char * SEMANTIC_MODIFIERS[] = { + "declaration", //1 + "definition", //2 + "static" //4 +}; + extern std::vector g_init_options; namespace { @@ -89,6 +130,14 @@ struct ServerCap { std::vector commands = {ccls_xref}; } executeCommandProvider; Config::ServerCap::Workspace workspace; + struct SemanticTokenProvider { + struct SemanticTokensLegend { + std::vector tokenTypes{std::begin(SEMANTIC_TOKENS), std::end(SEMANTIC_TOKENS)}; + std::vector tokenModifiers{std::begin(SEMANTIC_MODIFIERS), std::end(SEMANTIC_MODIFIERS)}; + } legend; + bool range = true; + bool full = true; + } semanticTokensProvider; }; REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds); REFLECT_STRUCT(ServerCap::CodeLensOptions, resolveProvider); @@ -109,7 +158,9 @@ REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider, documentRangeFormattingProvider, documentOnTypeFormattingProvider, renameProvider, documentLinkProvider, foldingRangeProvider, - executeCommandProvider, workspace); + executeCommandProvider, workspace, semanticTokensProvider); +REFLECT_STRUCT(ServerCap::SemanticTokenProvider, legend, range, full); +REFLECT_STRUCT(ServerCap::SemanticTokenProvider::SemanticTokensLegend, tokenTypes, tokenModifiers); struct DynamicReg { bool dynamicRegistration = false; diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc index c6f1035b9..215c7d430 100644 --- a/src/messages/textDocument_did.cc +++ b/src/messages/textDocument_did.cc @@ -36,7 +36,7 @@ void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { QueryFile *file = findFile(path); if (file) { emitSkippedRanges(wf, *file); - emitSemanticHighlight(db, wf, *file); + emitSemanticHighlightRefresh(); } include_complete->addFile(wf->filename); diff --git a/src/messages/textDocument_semanticToken.cc b/src/messages/textDocument_semanticToken.cc new file mode 100644 index 000000000..7b1bd9c9d --- /dev/null +++ b/src/messages/textDocument_semanticToken.cc @@ -0,0 +1,310 @@ +// Copyright ccls Authors +// SPDX-License-Identifier: Apache-2.0 + +#include "indexer.hh" +#include "log.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "sema_manager.hh" + +#include +#include +#include +#include + +MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind); + +namespace ccls { +REFLECT_STRUCT(QueryFile::SemanticTokens, data); +REFLECT_STRUCT(QueryFile::SemanticTokensWithId, tokens, id); + +using namespace clang; + +namespace { + +struct CclsSemanticHighlightSymbol { + int id = 0; + SymbolKind parentKind; + SymbolKind kind; + uint8_t storage; + std::vector> lsRangeAndRoles; +}; + +struct ScanLineEvent { + Position pos; + Position end_pos; // Second key when there is a tie for insertion events. + int id; + CclsSemanticHighlightSymbol *symbol; + Role role; + bool operator<(const ScanLineEvent &o) const { + // See the comments below when insertion/deletion events are inserted. + if (!(pos == o.pos)) + return pos < o.pos; + if (!(o.end_pos == end_pos)) + return o.end_pos < end_pos; + // This comparison essentially order Macro after non-Macro, + // So that macros will not be rendered as Var/Type/... + if (symbol->kind != o.symbol->kind) + return symbol->kind < o.symbol->kind; + // If symbol A and B occupy the same place, we want one to be placed + // before the other consistantly. + return symbol->id < o.symbol->id; + } +}; + +} // namespace +constexpr Position documentBegin{0, 0}; +constexpr Position documentEnd{ + std::numeric_limits::max(), + std::numeric_limits::max()}; + +inline std::ostream &operator<<(std::ostream &s, const Position pos) { + s << "{line: " << pos.line << ", end: " << pos.character << "}"; + return s; +} +inline std::ostream &operator<<(std::ostream &s, const lsRange &range) { + s << "lsRange(start:" << range.start << ", end:" << range.end << ")"; + return s; +} + +void MessageHandler::textDocument_semanticTokensRange( + SemanticTokensRangeParams ¶m, ReplyOnce &reply) { + const std::string path = param.textDocument.uri.getPath(); + if (param.range.start == documentBegin && param.range.end == documentEnd) + LOG_S(INFO) << "SemanticToken for all document of " << path; + else + LOG_S(INFO) << "SemanticToken for range " << param.range.start << " of " << path; + + WorkingFile *wfile = wfiles->getFile(path); + if (!wfile) { + reply.notOpened(path); + return; + } + + auto [queryFile, wFile] = findOrFail(path, reply); + if (!queryFile) { + // `findOrFail` already set the reply message + return; + } + + QueryFile::SemanticTokensWithId result; + + static GroupMatch match(g_config->highlight.whitelist, + g_config->highlight.blacklist); + assert(queryFile->def); + if (wfile->buffer_content.size() > g_config->highlight.largeFileSize || + !match.matches(queryFile->def->path)) { + LOG_S(INFO) << "Not SemTokenizing " << path + << "because of allowlist/denylist"; + return; + } + + // Group symbols together. + std::unordered_map grouped_symbols; + for (auto [sym, refcnt] : queryFile->symbol2refcnt) { + if (refcnt <= 0) + continue; + // skip symbols that don't intersect range + if (sym.range.end.line < param.range.start.line || + sym.range.start.line > param.range.end.line + // range is within lines here below, let's test if within specified + // characters/columns + || sym.range.end.column < param.range.start.character || + sym.range.start.column > param.range.end.character) + continue; + std::string_view detailed_name; + SymbolKind parent_kind = SymbolKind::Unknown; + SymbolKind kind = SymbolKind::Unknown; + uint8_t storage = SC_None; + decltype(db->func_usr)::key_type idx; + // This switch statement also filters out symbols that are not highlighted. + switch (sym.kind) { + case Kind::Func: { + idx = db->func_usr[sym.usr]; + const QueryFunc &func = db->funcs[idx]; + const QueryFunc::Def *def = func.anyDef(); + if (!def) + continue; // applies to for loop + // Don't highlight overloadable operators or implicit lambda -> + // std::function constructor. + const auto short_name = def->name(false); + if (short_name.compare(0, 8, "operator") == 0) + continue; // applies to for loop + kind = def->kind; + storage = def->storage; + detailed_name = short_name; + parent_kind = def->parent_kind; + + // Check whether the function name is actually there. + // If not, do not publish the semantic highlight. + // E.g. copy-initialization of constructors should not be highlighted + // but we still want to keep the range for jumping to definition. + const auto concise_name = + detailed_name.substr(0, detailed_name.find('<')); + const auto start_line_idx = sym.range.start.line; + const auto start_col = sym.range.start.column; + if (start_line_idx >= wfile->index_lines.size()) // out-of-range ? + continue; + const auto line = wfile->index_lines[start_line_idx]; + sym.range.end.line = start_line_idx; + if (!(start_col + concise_name.size() <= line.size() && + line.compare(start_col, concise_name.size(), concise_name) == 0)) + continue; + sym.range.end.column = start_col + concise_name.size(); + break; + } + case Kind::Type: { + idx = db->type_usr[sym.usr]; + const QueryType &type = db->types[idx]; + for (auto &def : type.def) { + kind = def.kind; + detailed_name = def.detailed_name; + if (def.spell) { + parent_kind = def.parent_kind; + break; + } + } + break; + } + case Kind::Var: { + idx = db->var_usr[sym.usr]; + const QueryVar &var = db->vars[idx]; + for (auto &def : var.def) { + kind = def.kind; + storage = def.storage; + detailed_name = def.detailed_name; + if (def.spell) { + parent_kind = def.parent_kind; + break; + } + } + break; + } + default: + continue; // applies to for loop + } + + if (auto maybe_loc = getLsRange(wfile, sym.range)) { + auto it = grouped_symbols.find(sym); + const auto &loc = *maybe_loc; + if (it != grouped_symbols.end()) { + it->second.lsRangeAndRoles.push_back({loc, sym.role}); + } else { + CclsSemanticHighlightSymbol symbol; + symbol.id = idx; + symbol.parentKind = parent_kind; + symbol.kind = kind; + symbol.storage = storage; + symbol.lsRangeAndRoles.push_back({loc, sym.role}); + grouped_symbols[sym] = symbol; + } + } + } + + // Make ranges non-overlapping using a scan line algorithm. + std::vector events; + int id = 0; + for (auto &entry : grouped_symbols) { + CclsSemanticHighlightSymbol &symbol = entry.second; + for (auto &loc : symbol.lsRangeAndRoles) { + // For ranges sharing the same start point, the one with leftmost end + // point comes first. + events.push_back( + {loc.first.start, loc.first.end, id, &symbol, loc.second}); + // For ranges sharing the same end point, their relative order does not + // matter, therefore we arbitrarily assign loc.end to them. We use + // negative id to indicate a deletion event. + events.push_back( + {loc.first.end, loc.first.end, ~id, &symbol, loc.second}); + id++; + } + symbol.lsRangeAndRoles.clear(); + } + std::sort(events.begin(), events.end()); + + std::vector deleted(id, 0); + int top = 0; + for (size_t i = 0; i < events.size(); i++) { + while (top && deleted[events[top - 1].id]) + top--; + // Order [a, b0) after [a, b1) if b0 < b1. The range comes later overrides + // the ealier. The order of [a0, b) [a1, b) does not matter. + // The order of [a, b) [b, c) does not as long as we do not emit empty + // ranges. + // Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol + // . + if (top && !(events[i - 1].pos == events[i].pos)) + events[top - 1].symbol->lsRangeAndRoles.push_back( + {{events[i - 1].pos, events[i].pos}, events[i].role}); + if (events[i].id >= 0) + events[top++] = events[i]; + else + deleted[~events[i].id] = 1; + } + + // Transform lsRange into pair (offset pairs) + std::vector< + std::pair, CclsSemanticHighlightSymbol *>> + scratch; + for (auto &entry : grouped_symbols) { + for (auto &range : entry.second.lsRangeAndRoles) + scratch.emplace_back(range, &entry.second); + entry.second.lsRangeAndRoles.clear(); + } + std::sort(scratch.begin(), scratch.end(), [](auto &l, auto &r) { + return l.first.first.start < r.first.first.start; + }); + int line = 0; + int column = 0; + for (auto &entry : scratch) { + lsRange &r = entry.first.first; + if (r.start.line != line) { + column = 0; + } + + auto &serialized = result.tokens.data; + + serialized.push_back(r.start.line - line); + line = r.start.line; + serialized.push_back(r.start.character - column); + column = r.start.character; + serialized.push_back(r.end.character - r.start.character); + + uint8_t kindId; + int modifiers = entry.second->storage == SC_Static ? 4 : 0; + + if (entry.first.second & Role::Declaration) + modifiers |= 1; + + if (entry.first.second & Role::Definition) + modifiers |= 2; + + if (entry.second->kind == SymbolKind::StaticMethod) { + kindId = (uint8_t)SymbolKind::Method; + modifiers = 4; + } else { + kindId = (uint8_t)entry.second->kind; + if (kindId > (uint8_t)SymbolKind::StaticMethod) + kindId--; + if (kindId >= 252) + kindId = 27 + kindId - 252; + } + serialized.push_back(kindId); + serialized.push_back(modifiers); + } + // tokens ready, let's tag them with "the next id" + result.id = queryFile->latestSemanticTokens.id + 1; + // before sending data, we'll cache the token we're sending + queryFile->latestSemanticTokens = result; + + reply(result.tokens); +} + +void MessageHandler::textDocument_semanticTokensFull( + SemanticTokensParams ¶m, ReplyOnce &reply) { + lsRange fullRange{documentBegin, documentEnd}; + SemanticTokensRangeParams fullRangeParameters{param.textDocument, fullRange}; + textDocument_semanticTokensRange(fullRangeParameters, reply); +} + +} // namespace ccls diff --git a/src/pipeline.cc b/src/pipeline.cc index 6f517b3fe..fc38888bc 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -497,8 +497,7 @@ void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) { std::string path = lowerPathIfInsensitive(f); if (db->name2file_id.find(path) == db->name2file_id.end()) continue; - QueryFile &file = db->files[db->name2file_id[path]]; - emitSemanticHighlight(db, wf.get(), file); + emitSemanticHighlightRefresh(); } return; } @@ -515,7 +514,7 @@ void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) { : def_u.second); QueryFile &file = db->files[update->file_id]; emitSkippedRanges(wfile, file); - emitSemanticHighlight(db, wfile, file); + emitSemanticHighlightRefresh(); } } } diff --git a/src/position.hh b/src/position.hh index e59804d2b..dee986576 100644 --- a/src/position.hh +++ b/src/position.hh @@ -21,14 +21,23 @@ struct Pos { // Compare two Positions and check if they are equal. Ignores the value of // |interesting|. bool operator==(const Pos &o) const { - return line == o.line && column == o.column; + return asTuple() == o.asTuple(); } bool operator<(const Pos &o) const { - if (line != o.line) - return line < o.line; - return column < o.column; + return asTuple() < o.asTuple(); } - bool operator<=(const Pos &o) const { return !(o < *this); } + bool operator<=(const Pos &o) const { + return asTuple() <= o.asTuple(); + } +protected: + /*! + * (line, pos) + * use for lexicographic comparison + */ + auto asTuple() const -> std::tuple { + return std::make_tuple(line, column); + } + }; struct Range { diff --git a/src/query.hh b/src/query.hh index cd3a19c17..ac88dc78b 100644 --- a/src/query.hh +++ b/src/query.hh @@ -42,8 +42,24 @@ struct QueryFile { int id = -1; std::optional def; - // `extent` is valid => declaration; invalid => regular reference - llvm::DenseMap symbol2refcnt; + //! `extent` is valid => declaration; invalid => regular reference + using SymbolToRefCount=llvm::DenseMap; + SymbolToRefCount symbol2refcnt; + + //! List of 5-integers that describe (line, column, length, token id, token mod) + struct SemanticTokens { + std::vector data; + }; + //! Semantic tokens with an id + struct SemanticTokensWithId { + using Id=int; + static constexpr Id invalidId=-1; + SemanticTokens tokens; + //! Id local to a file + Id id = invalidId; + }; + //! Latest tokens sent to the client + SemanticTokensWithId latestSemanticTokens; }; template struct QueryEntity { @@ -146,6 +162,7 @@ using Lid2file_id = std::unordered_map; struct DB { std::vector files; llvm::StringMap name2file_id; + //! Usr → index llvm::DenseMap func_usr, type_usr, var_usr; llvm::SmallVector funcs; llvm::SmallVector types;