From b2736f882249f03188327f7f10251237eb0ada00 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Sun, 3 Dec 2017 18:23:14 -0800 Subject: [PATCH] Semantic highlighting improvements. - Semantic highlighting no longer disappears when switching between files. - Semantic highlighting for a symbol will remain stable as the file is edited. - Improved semantic highlighting colors. Progress indicator also now shows the number of remaining index jobs (not the total number). --- src/clang_complete.cc | 51 ++++++------------------ src/clang_complete.h | 18 ++------- src/command_line.cc | 81 ++++++++++++++++++++++++++++++++++++--- src/language_server_api.h | 6 ++- 4 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 15992604..b5c2a745 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -495,35 +495,6 @@ CompletionSession::CompletionSession(const Project::Entry& file, CompletionSession::~CompletionSession() {} -LruSessionCache::LruSessionCache(int max_entries) : max_entries_(max_entries) {} - -std::shared_ptr LruSessionCache::TryGetEntry( - const std::string& filename) { - for (size_t i = 0; i < entries_.size(); ++i) { - if (entries_[i]->file.filename == filename) - return entries_[i]; - } - return nullptr; -} - -std::shared_ptr LruSessionCache::TryTakeEntry( - const std::string& filename) { - for (size_t i = 0; i < entries_.size(); ++i) { - if (entries_[i]->file.filename == filename) { - std::shared_ptr result = entries_[i]; - entries_.erase(entries_.begin() + i); - return result; - } - } - return nullptr; -} - -void LruSessionCache::InsertEntry(std::shared_ptr session) { - if (entries_.size() && entries_.size() >= max_entries_) - entries_.pop_back(); - entries_.insert(entries_.begin(), session); -} - ClangCompleteManager::ParseRequest::ParseRequest(const std::string& path) : request_time(std::chrono::high_resolution_clock::now()), path(path) {} @@ -622,10 +593,10 @@ void ClangCompleteManager::NotifyClose(const std::string& filename) { // Take and drop. It's okay if we don't actually drop the file, it'll // eventually get pushed out of the caches as the user opens other files. - auto preloaded_ptr = preloaded_sessions_.TryTakeEntry(filename); + auto preloaded_ptr = preloaded_sessions_.TryTake(filename); LOG_IF_S(INFO, !!preloaded_ptr) << "Dropped preloaded-based code completion session for " << filename; - auto completion_ptr = completion_sessions_.TryTakeEntry(filename); + auto completion_ptr = completion_sessions_.TryTake(filename); LOG_IF_S(INFO, !!completion_ptr) << "Dropped completion-based code completion session for " << filename; @@ -638,15 +609,15 @@ bool ClangCompleteManager::EnsureCompletionOrCreatePreloadSession( std::lock_guard lock(sessions_lock_); // Check for an existing CompletionSession. - if (preloaded_sessions_.TryGetEntry(filename) || - completion_sessions_.TryGetEntry(filename)) { + if (preloaded_sessions_.TryGet(filename) || + completion_sessions_.TryGet(filename)) { return false; } // No CompletionSession, create new one. auto session = std::make_shared( project_->FindCompilationEntryForFile(filename), working_files_); - preloaded_sessions_.InsertEntry(session); + preloaded_sessions_.Insert(session->file.filename, session); return true; } @@ -658,15 +629,15 @@ std::shared_ptr ClangCompleteManager::TryGetSession( // Try to find a preloaded session. std::shared_ptr preloaded_session = - preloaded_sessions_.TryGetEntry(filename); + preloaded_sessions_.TryGet(filename); if (preloaded_session) { // If this request is for a completion, we should move it to // |completion_sessions|. if (mark_as_completion) { - assert(!completion_sessions_.TryGetEntry(filename)); - preloaded_sessions_.TryTakeEntry(filename); - completion_sessions_.InsertEntry(preloaded_session); + assert(!completion_sessions_.TryGet(filename)); + preloaded_sessions_.TryTake(filename); + completion_sessions_.Insert(filename, preloaded_session); } return preloaded_session; @@ -674,11 +645,11 @@ std::shared_ptr ClangCompleteManager::TryGetSession( // Try to find a completion session. If none create one. std::shared_ptr completion_session = - completion_sessions_.TryGetEntry(filename); + completion_sessions_.TryGet(filename); if (!completion_session && create_if_needed) { completion_session = std::make_shared( project_->FindCompilationEntryForFile(filename), working_files_); - completion_sessions_.InsertEntry(completion_session); + completion_sessions_.Insert(filename, completion_session); } return completion_session; diff --git a/src/clang_complete.h b/src/clang_complete.h index f3ee569a..b7a029dd 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -2,6 +2,7 @@ #include "clang_index.h" #include "clang_translation_unit.h" #include "language_server_api.h" +#include "lru_cache.h" #include "project.h" #include "threaded_queue.h" #include "working_files.h" @@ -33,21 +34,6 @@ struct CompletionSession ~CompletionSession(); }; -struct LruSessionCache { - std::vector> entries_; - int max_entries_; - - LruSessionCache(int max_entries); - - // Fetches the entry for |filename| and updates it's usage so it is less - // likely to be evicted. - std::shared_ptr TryGetEntry(const std::string& filename); - // TryGetEntry, except the return value captures ownership. - std::shared_ptr TryTakeEntry(const std::string& fiilename); - // Inserts an entry. Evicts the oldest unused entry if there is no space. - void InsertEntry(std::shared_ptr session); -}; - struct ClangCompleteManager { using OnDiagnostic = std::function; + // CompletionSession instances which are preloaded, ie, files which the user // has viewed but not requested code completion for. LruSessionCache preloaded_sessions_; diff --git a/src/command_line.cc b/src/command_line.cc index bc1d60bc..25b433d0 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -7,6 +7,7 @@ #include "ipc_manager.h" #include "language_server_api.h" #include "lex_utils.h" +#include "lru_cache.h" #include "match.h" #include "options.h" #include "platform.h" @@ -144,9 +145,59 @@ void EmitInactiveLines(WorkingFile* working_file, IpcId::CqueryPublishInactiveRegions, out); } +// Caches symbols for a single file for semantic highlighting to provide +// relatively stable ids. Only supports xxx files at a time. +struct SemanticHighlightSymbolCache { + struct Entry { + // The path this cache belongs to. + std::string path; + // Detailed symbol name to stable id. + using TNameToId = std::unordered_map; + TNameToId detailed_type_name_to_stable_id; + TNameToId detailed_func_name_to_stable_id; + TNameToId detailed_var_name_to_stable_id; + + explicit Entry(const std::string& path) : path(path) {} + + int GetStableId(SymbolKind kind, const std::string& detailed_name) { + TNameToId* map = nullptr; + switch (kind) { + case SymbolKind::Type: + map = &detailed_type_name_to_stable_id; + break; + case SymbolKind::Func: + map = &detailed_func_name_to_stable_id; + break; + case SymbolKind::Var: + map = &detailed_var_name_to_stable_id; + break; + default: + assert(false); + return 0; + } + assert(map); + auto it = map->find(detailed_name); + if (it != map->end()) + return it->second; + return (*map)[detailed_name] = map->size(); + } + }; + + constexpr static int kCacheSize = 10; + LruCache cache_; + + SemanticHighlightSymbolCache() : cache_(kCacheSize) {} + + std::shared_ptr GetCacheForFile(const std::string& path) { + return cache_.Get(path, [&]() { return std::make_shared(path); }); + } +}; + void EmitSemanticHighlighting(QueryDatabase* db, + SemanticHighlightSymbolCache* semantic_cache, WorkingFile* working_file, QueryFile* file) { + assert(file->def); auto map_symbol_kind_to_symbol_type = [](SymbolKind kind) { switch (kind) { case SymbolKind::Type: @@ -161,11 +212,16 @@ void EmitSemanticHighlighting(QueryDatabase* db, } }; + auto semantic_cache_for_file = + semantic_cache->GetCacheForFile(file->def->path); + // Group symbols together. std::unordered_map grouped_symbols; for (SymbolRef sym : file->def->all_symbols) { + std::string detailed_name; bool is_type_member = false; + // This switch statement also filters out symbols that are not highlighted. switch (sym.idx.kind) { case SymbolKind::Func: { QueryFunc* func = &db->funcs[sym.idx.idx]; @@ -174,6 +230,7 @@ void EmitSemanticHighlighting(QueryDatabase* db, if (func->def->is_operator) continue; // applies to for loop is_type_member = func->def->declaring_type.has_value(); + detailed_name = func->def->short_name; break; } case SymbolKind::Var: { @@ -183,9 +240,14 @@ void EmitSemanticHighlighting(QueryDatabase* db, if (!var->def->is_local && !var->def->declaring_type) continue; // applies to for loop is_type_member = var->def->declaring_type.has_value(); + detailed_name = var->def->short_name; break; } case SymbolKind::Type: { + QueryType* type = &db->types[sym.idx.idx]; + if (!type->def) + continue; // applies to for loop + detailed_name = type->def->detailed_name; break; } default: @@ -199,8 +261,10 @@ void EmitSemanticHighlighting(QueryDatabase* db, it->second.ranges.push_back(*loc); } else { Out_CqueryPublishSemanticHighlighting::Symbol symbol; + symbol.stableId = + semantic_cache_for_file->GetStableId(sym.idx.kind, detailed_name); symbol.type = map_symbol_kind_to_symbol_type(sym.idx.kind); - symbol.is_type_member = is_type_member; + symbol.isTypeMember = is_type_member; symbol.ranges.push_back(*loc); grouped_symbols[sym.idx] = symbol; } @@ -1265,6 +1329,7 @@ bool QueryDb_ImportMain(Config* config, QueryDatabase* db, ImportManager* import_manager, QueueManager* queue, + SemanticHighlightSymbolCache* semantic_cache, WorkingFiles* working_files) { EmitProgress(config, queue); @@ -1369,7 +1434,7 @@ bool QueryDb_ImportMain(Config* config, QueryFileId file_id = db->usr_to_file[LowerPathIfCaseInsensitive(working_file->filename)]; QueryFile* file = &db->files[file_id.id]; - EmitSemanticHighlighting(db, working_file, file); + EmitSemanticHighlighting(db, semantic_cache, working_file, file); } } @@ -1409,6 +1474,7 @@ bool QueryDbMainLoop(Config* config, FileConsumer::SharedState* file_consumer_shared, ImportManager* import_manager, TimestampManager* timestamp_manager, + SemanticHighlightSymbolCache* semantic_cache, WorkingFiles* working_files, ClangCompleteManager* clang_complete, IncludeComplete* include_complete, @@ -1870,7 +1936,7 @@ bool QueryDbMainLoop(Config* config, FindFileOrFail(db, nullopt, path, &file); if (file && file->def) { EmitInactiveLines(working_file, file->def->inactive_regions); - EmitSemanticHighlighting(db, working_file, file); + EmitSemanticHighlighting(db, semantic_cache, working_file, file); } time.ResetAndPrint( @@ -2924,7 +2990,7 @@ bool QueryDbMainLoop(Config* config, has_work |= import_manager->HasActiveQuerydbImports(); has_work |= queue->HasWork(); has_work |= QueryDb_ImportMain(config, db, import_manager, queue, - working_files); + semantic_cache, working_files); if (!has_work) ++idle_count; else @@ -2956,8 +3022,10 @@ bool QueryDbMainLoop(Config* config, // TODO: consider rate-limiting and checking for IPC messages so we don't // block requests / we can serve partial requests. - if (QueryDb_ImportMain(config, db, import_manager, queue, working_files)) + if (QueryDb_ImportMain(config, db, import_manager, queue, semantic_cache, + working_files)) { did_work = true; + } return did_work; } @@ -2968,6 +3036,7 @@ void RunQueryDbThread(const std::string& bin_name, QueueManager* queue) { bool exit_when_idle = false; Project project; + SemanticHighlightSymbolCache semantic_cache; WorkingFiles working_files; FileConsumer::SharedState file_consumer_shared; @@ -2993,7 +3062,7 @@ void RunQueryDbThread(const std::string& bin_name, bool did_work = QueryDbMainLoop( config, &db, &exit_when_idle, waiter, queue, &project, &file_consumer_shared, &import_manager, ×tamp_manager, - &working_files, &clang_complete, &include_complete, + &semantic_cache, &working_files, &clang_complete, &include_complete, global_code_complete_cache.get(), non_global_code_complete_cache.get(), signature_cache.get()); diff --git a/src/language_server_api.h b/src/language_server_api.h index 08202910..9e2b6729 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -1517,8 +1517,9 @@ struct Out_CqueryPublishSemanticHighlighting : public lsOutMessage { enum class SymbolType { Type = 0, Function, Variable }; struct Symbol { + std::size_t stableId = 0; SymbolType type = SymbolType::Type; - bool is_type_member = false; + bool isTypeMember = false; NonElidedVector ranges; }; struct Params { @@ -1531,7 +1532,8 @@ struct Out_CqueryPublishSemanticHighlighting MAKE_REFLECT_TYPE_PROXY(Out_CqueryPublishSemanticHighlighting::SymbolType, int); MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting::Symbol, type, - is_type_member, + isTypeMember, + stableId, ranges); MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting::Params, uri,