From b441a90e0d30d637f56cb8a195e73055868b94d5 Mon Sep 17 00:00:00 2001 From: Jacob Dufault Date: Tue, 5 Dec 2017 19:32:33 -0800 Subject: [PATCH] Finish migrating to messages/ --- src/command_line.cc | 2019 +---------------- src/entry_points.h | 8 + src/lex_utils.cc | 129 ++ src/lru_cache.h | 2 + src/message_handler.cc | 208 +- src/message_handler.h | 22 + src/messages/cquery_base.cc | 42 + src/messages/cquery_call_tree.cc | 46 + src/messages/cquery_callers.cc | 34 + src/messages/cquery_derived.cc | 34 + src/messages/cquery_did_view.cc | 22 + src/messages/cquery_exit_when_idle.cc | 13 + src/messages/cquery_freshen_index.cc | 44 + src/messages/cquery_index_file.cc | 11 + .../cquery_querydb_wait_for_idle_indexer.cc | 32 + src/messages/cquery_type_hierarchy_tree.cc | 35 + src/messages/cquery_vars.cc | 29 + src/messages/text_document_code_action.cc | 546 +++++ src/messages/text_document_code_lens.cc | 141 ++ src/messages/text_document_completion.cc | 124 + src/messages/text_document_definition.cc | 97 + src/messages/text_document_did_change.cc | 13 + src/messages/text_document_did_close.cc | 18 + src/messages/text_document_did_open.cc | 41 + src/messages/text_document_did_save.cc | 27 + src/messages/text_document_document_link.cc | 51 + src/messages/text_document_document_symbol.cc | 32 + src/messages/text_document_highlight.cc | 46 + src/messages/text_document_hover.cc | 36 + src/messages/text_document_references.cc | 47 + src/messages/text_document_rename.cc | 32 + src/messages/text_document_signature_help.cc | 86 + src/messages/workspace_symbol.cc | 56 + src/query.cc | 2 +- src/semantic_highlight_symbol_cache.cc | 37 + src/semantic_highlight_symbol_cache.h | 32 + 36 files changed, 2178 insertions(+), 2016 deletions(-) create mode 100644 src/messages/cquery_base.cc create mode 100644 src/messages/cquery_call_tree.cc create mode 100644 src/messages/cquery_callers.cc create mode 100644 src/messages/cquery_derived.cc create mode 100644 src/messages/cquery_did_view.cc create mode 100644 src/messages/cquery_exit_when_idle.cc create mode 100644 src/messages/cquery_freshen_index.cc create mode 100644 src/messages/cquery_index_file.cc create mode 100644 src/messages/cquery_querydb_wait_for_idle_indexer.cc create mode 100644 src/messages/cquery_type_hierarchy_tree.cc create mode 100644 src/messages/cquery_vars.cc create mode 100644 src/messages/text_document_code_action.cc create mode 100644 src/messages/text_document_code_lens.cc create mode 100644 src/messages/text_document_completion.cc create mode 100644 src/messages/text_document_definition.cc create mode 100644 src/messages/text_document_did_change.cc create mode 100644 src/messages/text_document_did_close.cc create mode 100644 src/messages/text_document_did_open.cc create mode 100644 src/messages/text_document_did_save.cc create mode 100644 src/messages/text_document_document_link.cc create mode 100644 src/messages/text_document_document_symbol.cc create mode 100644 src/messages/text_document_highlight.cc create mode 100644 src/messages/text_document_hover.cc create mode 100644 src/messages/text_document_references.cc create mode 100644 src/messages/text_document_rename.cc create mode 100644 src/messages/text_document_signature_help.cc create mode 100644 src/messages/workspace_symbol.cc create mode 100644 src/semantic_highlight_symbol_cache.cc create mode 100644 src/semantic_highlight_symbol_cache.h diff --git a/src/command_line.cc b/src/command_line.cc index 9cacad12..a12d4cd5 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -69,430 +69,6 @@ bool ShouldDisplayIpcTiming(IpcId id) { } } -void PushBack(NonElidedVector* result, - optional location) { - if (location) - result->push_back(*location); -} - -bool FindFileOrFail(QueryDatabase* db, - optional id, - const std::string& absolute_path, - QueryFile** out_query_file, - QueryFileId* out_file_id = nullptr) { - *out_query_file = nullptr; - - auto it = db->usr_to_file.find(LowerPathIfCaseInsensitive(absolute_path)); - if (it != db->usr_to_file.end()) { - QueryFile& file = db->files[it->second.id]; - if (file.def) { - *out_query_file = &file; - if (out_file_id) - *out_file_id = QueryFileId(it->second.id); - return true; - } - } - - if (out_file_id) - *out_file_id = QueryFileId((size_t)-1); - - LOG_S(INFO) << "Unable to find file \"" << absolute_path << "\""; - - if (id) { - Out_Error out; - out.id = *id; - out.error.code = lsErrorCodes::InternalError; - out.error.message = "Unable to find file " + absolute_path; - IpcManager::WriteStdout(IpcId::Unknown, out); - } - - return false; -} - -void EmitInactiveLines(WorkingFile* working_file, - const std::vector& inactive_regions) { - Out_CquerySetInactiveRegion out; - out.params.uri = lsDocumentUri::FromPath(working_file->filename); - for (Range skipped : inactive_regions) { - optional ls_skipped = GetLsRange(working_file, skipped); - if (ls_skipped) - out.params.inactiveRegions.push_back(*ls_skipped); - } - IpcManager::WriteStdout(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: - return Out_CqueryPublishSemanticHighlighting::SymbolType::Type; - case SymbolKind::Func: - return Out_CqueryPublishSemanticHighlighting::SymbolType::Function; - case SymbolKind::Var: - return Out_CqueryPublishSemanticHighlighting::SymbolType::Variable; - default: - assert(false); - return Out_CqueryPublishSemanticHighlighting::SymbolType::Variable; - } - }; - - 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]; - if (!func->def) - continue; // applies to for loop - 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: { - QueryVar* var = &db->vars[sym.idx.idx]; - if (!var->def) - continue; // applies to for loop - 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: - continue; // applies to for loop - } - - optional loc = GetLsRange(working_file, sym.loc.range); - if (loc) { - auto it = grouped_symbols.find(sym.idx); - if (it != grouped_symbols.end()) { - 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.isTypeMember = is_type_member; - symbol.ranges.push_back(*loc); - grouped_symbols[sym.idx] = symbol; - } - } - } - - // Publish. - Out_CqueryPublishSemanticHighlighting out; - out.params.uri = lsDocumentUri::FromPath(working_file->filename); - for (auto& entry : grouped_symbols) - out.params.symbols.push_back(entry.second); - IpcManager::WriteStdout(IpcId::CqueryPublishSemanticHighlighting, out); -} - -optional FindIncludeLine(const std::vector& lines, - const std::string& full_include_line) { - // - // This returns an include line. For example, - // - // #include // 0 - // #include // 1 - // - // Given #include , this will return '1', which means that the - // #include text should be inserted at the start of line 1. Inserting - // at the start of a line allows insertion at both the top and bottom of the - // document. - // - // If the include line is already in the document this returns nullopt. - // - - optional last_include_line; - optional best_include_line; - - // 1 => include line is gt content (ie, it should go after) - // -1 => include line is lt content (ie, it should go before) - int last_line_compare = 1; - - for (int line = 0; line < (int)lines.size(); ++line) { - if (!StartsWith(lines[line], "#include")) { - last_line_compare = 1; - continue; - } - - last_include_line = line; - - int current_line_compare = full_include_line.compare(lines[line]); - if (current_line_compare == 0) - return nullopt; - - if (last_line_compare == 1 && current_line_compare == -1) - best_include_line = line; - last_line_compare = current_line_compare; - } - - if (best_include_line) - return *best_include_line; - // If |best_include_line| didn't match that means we likely didn't find an - // include which was lt the new one, so put it at the end of the last include - // list. - if (last_include_line) - return *last_include_line + 1; - // No includes, use top of document. - return 0; -} - -optional GetImplementationFile(QueryDatabase* db, - QueryFileId file_id, - QueryFile* file) { - for (SymbolRef sym : file->def->outline) { - switch (sym.idx.kind) { - case SymbolKind::Func: { - QueryFunc& func = db->funcs[sym.idx.idx]; - // Note: we ignore the definition if it is in the same file (ie, - // possibly a header). - if (func.def && func.def->definition_extent && - func.def->definition_extent->path != file_id) { - return func.def->definition_extent->path; - } - break; - } - case SymbolKind::Var: { - QueryVar& var = db->vars[sym.idx.idx]; - // Note: we ignore the definition if it is in the same file (ie, - // possibly a header). - if (var.def && var.def->definition_extent && - var.def->definition_extent->path != file_id) { - return db->vars[sym.idx.idx].def->definition_extent->path; - } - break; - } - default: - break; - } - } - - // No associated definition, scan the project for a file in the same - // directory with the same base-name. - std::string original_path = LowerPathIfCaseInsensitive(file->def->path); - std::string target_path = original_path; - size_t last = target_path.find_last_of('.'); - if (last != std::string::npos) { - target_path = target_path.substr(0, last); - } - - LOG_S(INFO) << "!! Looking for impl file that starts with " << target_path; - - for (auto& entry : db->usr_to_file) { - Usr path = entry.first; - - // Do not consider header files for implementation files. - // TODO: make file extensions configurable. - if (EndsWith(path, ".h") || EndsWith(path, ".hpp")) - continue; - - if (StartsWith(path, target_path) && path != original_path) { - return entry.second; - } - } - - return nullopt; -} - -void EnsureImplFile(QueryDatabase* db, - QueryFileId file_id, - optional& impl_uri, - optional& impl_file_id) { - if (!impl_uri.has_value()) { - QueryFile& file = db->files[file_id.id]; - assert(file.def); - - impl_file_id = GetImplementationFile(db, file_id, &file); - if (!impl_file_id.has_value()) - impl_file_id = file_id; - - QueryFile& impl_file = db->files[impl_file_id->id]; - if (impl_file.def) - impl_uri = lsDocumentUri::FromPath(impl_file.def->path); - else - impl_uri = lsDocumentUri::FromPath(file.def->path); - } -} - -optional BuildAutoImplementForFunction(QueryDatabase* db, - WorkingFiles* working_files, - WorkingFile* working_file, - int default_line, - QueryFileId decl_file_id, - QueryFileId impl_file_id, - QueryFunc& func) { - assert(func.def); - for (const QueryLocation& decl : func.declarations) { - if (decl.path != decl_file_id) - continue; - - optional ls_decl = GetLsRange(working_file, decl.range); - if (!ls_decl) - continue; - - optional type_name; - optional same_file_insert_end; - if (func.def->declaring_type) { - QueryType& declaring_type = db->types[func.def->declaring_type->id]; - if (declaring_type.def) { - type_name = declaring_type.def->short_name; - optional ls_type_def_extent = GetLsRange( - working_file, declaring_type.def->definition_extent->range); - if (ls_type_def_extent) { - same_file_insert_end = ls_type_def_extent->end; - same_file_insert_end->character += 1; // move past semicolon. - } - } - } - - std::string insert_text; - int newlines_after_name = 0; - LexFunctionDeclaration(working_file->buffer_content, ls_decl->start, - type_name, &insert_text, &newlines_after_name); - - if (!same_file_insert_end) { - same_file_insert_end = ls_decl->end; - same_file_insert_end->line += newlines_after_name; - same_file_insert_end->character = 1000; - } - - lsTextEdit edit; - - if (decl_file_id == impl_file_id) { - edit.range.start = *same_file_insert_end; - edit.range.end = *same_file_insert_end; - edit.newText = "\n\n" + insert_text; - } else { - lsPosition best_pos; - best_pos.line = default_line; - int best_dist = INT_MAX; - - QueryFile& file = db->files[impl_file_id.id]; - assert(file.def); - for (SymbolRef sym : file.def->outline) { - switch (sym.idx.kind) { - case SymbolKind::Func: { - QueryFunc& sym_func = db->funcs[sym.idx.idx]; - if (!sym_func.def || !sym_func.def->definition_extent) - break; - - for (QueryLocation& func_decl : sym_func.declarations) { - if (func_decl.path == decl_file_id) { - int dist = func_decl.range.start.line - decl.range.start.line; - if (abs(dist) < abs(best_dist)) { - optional def_loc = GetLsLocation( - db, working_files, *sym_func.def->definition_extent); - if (!def_loc) - continue; - - best_dist = dist; - - if (dist > 0) - best_pos = def_loc->range.start; - else - best_pos = def_loc->range.end; - } - } - } - - break; - } - case SymbolKind::Var: { - // TODO: handle vars. - break; - } - case SymbolKind::Invalid: - case SymbolKind::File: - case SymbolKind::Type: - LOG_S(WARNING) << "Unexpected SymbolKind " - << static_cast(sym.idx.kind); - break; - } - } - - edit.range.start = best_pos; - edit.range.end = best_pos; - if (best_dist < 0) - edit.newText = "\n\n" + insert_text; - else - edit.newText = insert_text + "\n\n"; - } - - return edit; - } - - return nullopt; -} - void EmitDiagnostics(WorkingFiles* working_files, std::string path, NonElidedVector diagnostics) { @@ -509,78 +85,6 @@ void EmitDiagnostics(WorkingFiles* working_files, }); } -// Pre-filters completion responses before sending to vscode. This results in a -// significantly snappier completion experience as vscode is easily overloaded -// when given 1000+ completion items. -void FilterCompletionResponse(Out_TextDocumentComplete* complete_response, - const std::string& complete_text) { -// Used to inject more completions. -#if false - const size_t kNumIterations = 250; - size_t size = complete_response->result.items.size(); - complete_response->result.items.reserve(size * (kNumIterations + 1)); - for (size_t iteration = 0; iteration < kNumIterations; ++iteration) { - for (size_t i = 0; i < size; ++i) { - auto item = complete_response->result.items[i]; - item.label += "#" + std::to_string(iteration); - complete_response->result.items.push_back(item); - } - } -#endif - - const size_t kMaxResultSize = 100u; - if (complete_response->result.items.size() > kMaxResultSize) { - if (complete_text.empty()) { - complete_response->result.items.resize(kMaxResultSize); - } else { - NonElidedVector filtered_result; - filtered_result.reserve(kMaxResultSize); - - std::unordered_set inserted; - inserted.reserve(kMaxResultSize); - - // Find literal matches first. - for (const lsCompletionItem& item : complete_response->result.items) { - if (item.label.find(complete_text) != std::string::npos) { - // Don't insert the same completion entry. - if (!inserted.insert(item.InsertedContent()).second) - continue; - - filtered_result.push_back(item); - if (filtered_result.size() >= kMaxResultSize) - break; - } - } - - // Find fuzzy matches if we haven't found all of the literal matches. - if (filtered_result.size() < kMaxResultSize) { - for (const lsCompletionItem& item : complete_response->result.items) { - if (SubstringMatch(complete_text, item.label)) { - // Don't insert the same completion entry. - if (!inserted.insert(item.InsertedContent()).second) - continue; - - filtered_result.push_back(item); - if (filtered_result.size() >= kMaxResultSize) - break; - } - } - } - - complete_response->result.items = filtered_result; - } - - // Assuming the client does not support out-of-order completion (ie, ao - // matches against oa), our filtering is guaranteed to contain any - // potential matches, so the completion is only incomplete if we have the - // max number of emitted matches. - if (complete_response->result.items.size() >= kMaxResultSize) { - LOG_S(INFO) << "Marking completion results as incomplete"; - complete_response->result.isIncomplete = true; - } - } -} - void RegisterMessageTypes() { // TODO: use automatic registration similar to MessageHandler. MessageRegistry::instance()->Register(); @@ -1245,1339 +749,10 @@ bool QueryDbMainLoop(Config* config, break; } } - if (!message) - continue; - // FIXME: assert(!message), ie, verify that a handler was run. - - switch (message->method_id) { - case IpcId::CqueryFreshenIndex: { - LOG_S(INFO) << "Freshening " << project->entries.size() << " files"; - - // TODO: think about this flow and test it more. - - // Unmark all files whose timestamp has changed. - CacheLoader cache_loader(config); - for (const auto& file : db->files) { - if (!file.def) - continue; - - optional modification_timestamp = - GetLastModificationTime(file.def->path); - if (!modification_timestamp) - continue; - - optional cached_modification = - timestamp_manager->GetLastCachedModificationTime(&cache_loader, - file.def->path); - if (modification_timestamp != cached_modification) - file_consumer_shared->Reset(file.def->path); - } - - // Send index requests for every file. - project->ForAllFilteredFiles( - config, [&](int i, const Project::Entry& entry) { - LOG_S(INFO) << "[" << i << "/" << (project->entries.size() - 1) - << "] Dispatching index request for file " - << entry.filename; - bool is_interactive = - working_files->GetFileByFilename(entry.filename) != nullptr; - queue->index_request.Enqueue(Index_Request( - entry.filename, entry.args, is_interactive, nullopt)); - }); - break; - } - - case IpcId::CqueryTypeHierarchyTree: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_CqueryTypeHierarchyTree out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Type) { - out.result = BuildInheritanceHierarchyForType( - db, working_files, QueryTypeId(ref.idx.idx)); - break; - } - if (ref.idx.kind == SymbolKind::Func) { - out.result = BuildInheritanceHierarchyForFunc( - db, working_files, QueryFuncId(ref.idx.idx)); - break; - } - } - - IpcManager::WriteStdout(IpcId::CqueryTypeHierarchyTree, out); - break; - } - - case IpcId::CqueryCallTreeInitial: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_CqueryCallTree out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Func) { - out.result = BuildInitialCallTree(db, working_files, - QueryFuncId(ref.idx.idx)); - break; - } - } - - IpcManager::WriteStdout(IpcId::CqueryCallTreeInitial, out); - break; - } - - case IpcId::CqueryCallTreeExpand: { - auto msg = message->As(); - - Out_CqueryCallTree out; - out.id = msg->id; - - auto func_id = db->usr_to_func.find(msg->params.usr); - if (func_id != db->usr_to_func.end()) - out.result = BuildExpandCallTree(db, working_files, func_id->second); - - IpcManager::WriteStdout(IpcId::CqueryCallTreeExpand, out); - break; - } - - case IpcId::CqueryVars: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = msg->id; - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Type) { - QueryType& type = db->types[ref.idx.idx]; - std::vector locations = - ToQueryLocation(db, type.instances); - out.result = GetLsLocations(db, working_files, locations); - } - } - IpcManager::WriteStdout(IpcId::CqueryVars, out); - break; - } - - case IpcId::CqueryCallers: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = msg->id; - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Func) { - QueryFunc& func = db->funcs[ref.idx.idx]; - std::vector locations = - ToQueryLocation(db, func.callers); - for (QueryFuncRef func_ref : - GetCallersForAllBaseFunctions(db, func)) - locations.push_back(func_ref.loc); - for (QueryFuncRef func_ref : - GetCallersForAllDerivedFunctions(db, func)) - locations.push_back(func_ref.loc); - - out.result = GetLsLocations(db, working_files, locations); - } - } - IpcManager::WriteStdout(IpcId::CqueryCallers, out); - break; - } - - case IpcId::CqueryBase: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = msg->id; - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Type) { - QueryType& type = db->types[ref.idx.idx]; - if (!type.def) - continue; - std::vector locations = - ToQueryLocation(db, type.def->parents); - out.result = GetLsLocations(db, working_files, locations); - } else if (ref.idx.kind == SymbolKind::Func) { - QueryFunc& func = db->funcs[ref.idx.idx]; - optional location = - GetBaseDefinitionOrDeclarationSpelling(db, func); - if (!location) - continue; - optional ls_loc = - GetLsLocation(db, working_files, *location); - if (!ls_loc) - continue; - out.result.push_back(*ls_loc); - } - } - IpcManager::WriteStdout(IpcId::CqueryBase, out); - break; - } - - case IpcId::CqueryDerived: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = msg->id; - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - if (ref.idx.kind == SymbolKind::Type) { - QueryType& type = db->types[ref.idx.idx]; - std::vector locations = - ToQueryLocation(db, type.derived); - out.result = GetLsLocations(db, working_files, locations); - } else if (ref.idx.kind == SymbolKind::Func) { - QueryFunc& func = db->funcs[ref.idx.idx]; - std::vector locations = - ToQueryLocation(db, func.derived); - out.result = GetLsLocations(db, working_files, locations); - } - } - IpcManager::WriteStdout(IpcId::CqueryDerived, out); - break; - } - - case IpcId::TextDocumentDidOpen: { - // NOTE: This function blocks code lens. If it starts taking a long time - // we will need to find a way to unblock the code lens request. - - Timer time; - auto msg = message->As(); - std::string path = msg->params.textDocument.uri.GetPath(); - WorkingFile* working_file = working_files->OnOpen(msg->params); - optional cached_file_contents = - LoadCachedFileContents(config, path); - if (cached_file_contents) - working_file->SetIndexContent(*cached_file_contents); - else - working_file->SetIndexContent(working_file->buffer_content); - - QueryFile* file = nullptr; - FindFileOrFail(db, nullopt, path, &file); - if (file && file->def) { - EmitInactiveLines(working_file, file->def->inactive_regions); - EmitSemanticHighlighting(db, semantic_cache, working_file, file); - } - - time.ResetAndPrint( - "[querydb] Loading cached index file for DidOpen (blocks " - "CodeLens)"); - - include_complete->AddFile(working_file->filename); - clang_complete->NotifyView(path); - - // Submit new index request. - const Project::Entry& entry = - project->FindCompilationEntryForFile(path); - queue->index_request.PriorityEnqueue(Index_Request( - entry.filename, entry.args, true /*is_interactive*/, nullopt)); - - break; - } - - case IpcId::CqueryTextDocumentDidView: { - auto msg = message->As(); - std::string path = msg->params.textDocumentUri.GetPath(); - - WorkingFile* working_file = working_files->GetFileByFilename(path); - if (!working_file) - break; - QueryFile* file = nullptr; - if (!FindFileOrFail(db, nullopt, path, &file)) - break; - - clang_complete->NotifyView(path); - if (file->def) { - EmitInactiveLines(working_file, file->def->inactive_regions); - EmitSemanticHighlighting(db, semantic_cache, working_file, file); - } - break; - } - - case IpcId::TextDocumentDidChange: { - auto msg = message->As(); - std::string path = msg->params.textDocument.uri.GetPath(); - working_files->OnChange(msg->params); - clang_complete->NotifyEdit(path); - clang_complete->DiagnosticsUpdate( - msg->params.textDocument.AsTextDocumentIdentifier()); - break; - } - - case IpcId::TextDocumentDidClose: { - auto msg = message->As(); - std::string path = msg->params.textDocument.uri.GetPath(); - - // Clear any diagnostics for the file. - Out_TextDocumentPublishDiagnostics out; - out.params.uri = msg->params.textDocument.uri; - IpcManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); - - // Remove internal state. - working_files->OnClose(msg->params); - clang_complete->NotifyClose(path); - - break; - } - - case IpcId::TextDocumentDidSave: { - auto msg = message->As(); - - std::string path = msg->params.textDocument.uri.GetPath(); - // Send out an index request, and copy the current buffer state so we - // can update the cached index contents when the index is done. - // - // We also do not index if there is already an index request. - // - // TODO: Cancel outgoing index request. Might be tricky to make - // efficient since we have to cancel. - // - we could have an |atomic active_cancellations| variable - // that all of the indexers check before accepting an index. if - // zero we don't slow down fast-path. if non-zero we acquire - // mutex and check to see if we should skip the current request. - // if so, ignore that index response. - // TODO: send as priority request - Project::Entry entry = project->FindCompilationEntryForFile(path); - queue->index_request.Enqueue(Index_Request( - entry.filename, entry.args, true /*is_interactive*/, nullopt)); - - clang_complete->NotifySave(path); - - break; - } - - case IpcId::TextDocumentRename: { - auto msg = message->As(); - - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file, &file_id)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentRename out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - // Found symbol. Return references to rename. - std::vector uses = GetUsesOfSymbol(db, ref.idx); - out.result = - BuildWorkspaceEdit(db, working_files, uses, msg->params.newName); - break; - } - - IpcManager::WriteStdout(IpcId::TextDocumentRename, out); - break; - } - - case IpcId::TextDocumentCompletion: { - auto msg = std::shared_ptr( - static_cast(message.release())); - - std::string path = msg->params.textDocument.uri.GetPath(); - WorkingFile* file = working_files->GetFileByFilename(path); - - // It shouldn't be possible, but sometimes vscode will send queries out - // of order, ie, we get completion request before buffer content update. - std::string buffer_line; - if (msg->params.position.line >= 0 && - msg->params.position.line < file->all_buffer_lines.size()) - buffer_line = file->all_buffer_lines[msg->params.position.line]; - - if (ShouldRunIncludeCompletion(buffer_line)) { - Out_TextDocumentComplete out; - out.id = msg->id; - - { - std::unique_lock lock( - include_complete->completion_items_mutex, std::defer_lock); - if (include_complete->is_scanning) - lock.lock(); - out.result.items.assign(include_complete->completion_items.begin(), - include_complete->completion_items.end()); - if (lock) - lock.unlock(); - - // Update textEdit params. - for (lsCompletionItem& item : out.result.items) { - item.textEdit->range.start.line = msg->params.position.line; - item.textEdit->range.start.character = 0; - item.textEdit->range.end.line = msg->params.position.line; - item.textEdit->range.end.character = (int)buffer_line.size(); - } - } - - FilterCompletionResponse(&out, buffer_line); - IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out); - } else { - bool is_global_completion = false; - std::string existing_completion; - if (file) { - msg->params.position = file->FindStableCompletionSource( - msg->params.position, &is_global_completion, - &existing_completion); - } - - ClangCompleteManager::OnComplete callback = std::bind( - [global_code_complete_cache, non_global_code_complete_cache, - is_global_completion, existing_completion, - msg](const NonElidedVector& results, - bool is_cached_result) { - Out_TextDocumentComplete out; - out.id = msg->id; - out.result.items = results; - - // Emit completion results. - FilterCompletionResponse(&out, existing_completion); - IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out); - - // Cache completion results. - if (!is_cached_result) { - std::string path = msg->params.textDocument.uri.GetPath(); - if (is_global_completion) { - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_path_ = path; - global_code_complete_cache->cached_results_ = results; - }); - } else { - non_global_code_complete_cache->WithLock([&]() { - non_global_code_complete_cache->cached_path_ = path; - non_global_code_complete_cache - ->cached_completion_position_ = msg->params.position; - non_global_code_complete_cache->cached_results_ = results; - }); - } - } - }, - std::placeholders::_1, std::placeholders::_2); - - bool is_cache_match = false; - global_code_complete_cache->WithLock([&]() { - is_cache_match = - is_global_completion && - global_code_complete_cache->cached_path_ == path && - !global_code_complete_cache->cached_results_.empty(); - }); - if (is_cache_match) { - ClangCompleteManager::OnComplete freshen_global = - [global_code_complete_cache]( - NonElidedVector results, - bool is_cached_result) { - assert(!is_cached_result); - - // note: path is updated in the normal completion handler. - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_results_ = results; - }); - }; - - global_code_complete_cache->WithLock([&]() { - callback(global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - clang_complete->CodeComplete(msg->params, freshen_global); - } else if (non_global_code_complete_cache->IsCacheValid( - msg->params)) { - non_global_code_complete_cache->WithLock([&]() { - callback(non_global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(msg->params, callback); - } - } - - break; - } - - case IpcId::TextDocumentSignatureHelp: { - auto msg = message->As(); - lsTextDocumentPositionParams& params = msg->params; - WorkingFile* file = - working_files->GetFileByFilename(params.textDocument.uri.GetPath()); - std::string search; - int active_param = 0; - if (file) { - lsPosition completion_position; - search = file->FindClosestCallNameInBuffer( - params.position, &active_param, &completion_position); - params.position = completion_position; - } - if (search.empty()) - break; - - ClangCompleteManager::OnComplete callback = std::bind( - [signature_cache](BaseIpcMessage* message, std::string search, - int active_param, - const NonElidedVector& results, - bool is_cached_result) { - auto msg = message->As(); - - Out_TextDocumentSignatureHelp out; - out.id = msg->id; - - for (auto& result : results) { - if (result.label != search) - continue; - - lsSignatureInformation signature; - signature.label = result.detail; - for (auto& parameter : result.parameters_) { - lsParameterInformation ls_param; - ls_param.label = parameter; - signature.parameters.push_back(ls_param); - } - out.result.signatures.push_back(signature); - } - - // Guess the signature the user wants based on available parameter - // count. - out.result.activeSignature = 0; - for (size_t i = 0; i < out.result.signatures.size(); ++i) { - if (active_param < out.result.signatures.size()) { - out.result.activeSignature = (int)i; - break; - } - } - - // Set signature to what we parsed from the working file. - out.result.activeParameter = active_param; - - Timer timer; - IpcManager::WriteStdout(IpcId::TextDocumentSignatureHelp, out); - - if (!is_cached_result) { - signature_cache->WithLock([&]() { - signature_cache->cached_path_ = - msg->params.textDocument.uri.GetPath(); - signature_cache->cached_completion_position_ = - msg->params.position; - signature_cache->cached_results_ = results; - }); - } - - delete message; - }, - message.release(), search, active_param, std::placeholders::_1, - std::placeholders::_2); - - if (signature_cache->IsCacheValid(params)) { - signature_cache->WithLock([&]() { - callback(signature_cache->cached_results_, - true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(params, std::move(callback)); - } - - break; - } - - case IpcId::TextDocumentDefinition: { - auto msg = message->As(); - - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file, &file_id)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentDefinition out; - out.id = msg->id; - - int target_line = msg->params.position.line + 1; - int target_column = msg->params.position.character + 1; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - // Found symbol. Return definition. - - // Special cases which are handled: - // - symbol has declaration but no definition (ie, pure virtual) - // - start at spelling but end at extent for better mouse tooltip - // - goto declaration while in definition of recursive type - - optional def_loc = - GetDefinitionSpellingOfSymbol(db, ref.idx); - - // We use spelling start and extent end because this causes vscode to - // highlight the entire definition when previewing / hoving with the - // mouse. - optional def_extent = - GetDefinitionExtentOfSymbol(db, ref.idx); - if (def_loc && def_extent) - def_loc->range.end = def_extent->range.end; - - // If the cursor is currently at or in the definition we should goto - // the declaration if possible. We also want to use declarations if - // we're pointing to, ie, a pure virtual function which has no - // definition. - if (!def_loc || - (def_loc->path == file_id && - def_loc->range.Contains(target_line, target_column))) { - // Goto declaration. - - std::vector declarations = - GetDeclarationsOfSymbolForGotoDefinition(db, ref.idx); - for (auto declaration : declarations) { - optional ls_declaration = - GetLsLocation(db, working_files, declaration); - if (ls_declaration) - out.result.push_back(*ls_declaration); - } - // We found some declarations. Break so we don't add the definition - // location. - if (!out.result.empty()) - break; - } - - if (def_loc) { - PushBack(&out.result, GetLsLocation(db, working_files, *def_loc)); - } - - if (!out.result.empty()) - break; - } - - // No symbols - check for includes. - if (out.result.empty()) { - for (const IndexInclude& include : file->def->includes) { - if (include.line == target_line) { - lsLocation result; - result.uri = lsDocumentUri::FromPath(include.resolved_path); - out.result.push_back(result); - break; - } - } - } - - IpcManager::WriteStdout(IpcId::TextDocumentDefinition, out); - break; - } - - case IpcId::TextDocumentDocumentHighlight: { - auto msg = message->As(); - - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file, &file_id)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentDocumentHighlight out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - // Found symbol. Return references to highlight. - std::vector uses = GetUsesOfSymbol(db, ref.idx); - out.result.reserve(uses.size()); - for (const QueryLocation& use : uses) { - if (use.path != file_id) - continue; - - optional ls_location = - GetLsLocation(db, working_files, use); - if (!ls_location) - continue; - - lsDocumentHighlight highlight; - highlight.kind = lsDocumentHighlightKind::Text; - highlight.range = ls_location->range; - out.result.push_back(highlight); - } - break; - } - - IpcManager::WriteStdout(IpcId::TextDocumentDocumentHighlight, out); - break; - } - - case IpcId::TextDocumentHover: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentHover out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - // Found symbol. Return hover. - optional ls_range = GetLsRange( - working_files->GetFileByFilename(file->def->path), ref.loc.range); - if (!ls_range) - continue; - - out.result.contents.value = GetHoverForSymbol(db, ref.idx); - out.result.contents.language = file->def->language; - - out.result.range = *ls_range; - break; - } - - IpcManager::WriteStdout(IpcId::TextDocumentHover, out); - break; - } - - case IpcId::TextDocumentReferences: { - auto msg = message->As(); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentReferences out; - out.id = msg->id; - - for (const SymbolRef& ref : - FindSymbolsAtLocation(working_file, file, msg->params.position)) { - optional excluded_declaration; - if (!msg->params.context.includeDeclaration) { - LOG_S(INFO) << "Excluding declaration in references"; - excluded_declaration = GetDefinitionSpellingOfSymbol(db, ref.idx); - } - - // Found symbol. Return references. - std::vector uses = GetUsesOfSymbol(db, ref.idx); - out.result.reserve(uses.size()); - for (const QueryLocation& use : uses) { - if (excluded_declaration.has_value() && - use == *excluded_declaration) - continue; - - optional ls_location = - GetLsLocation(db, working_files, use); - if (ls_location) - out.result.push_back(*ls_location); - } - break; - } - - IpcManager::WriteStdout(IpcId::TextDocumentReferences, out); - break; - } - - case IpcId::TextDocumentDocumentSymbol: { - auto msg = message->As(); - - Out_TextDocumentDocumentSymbol out; - out.id = msg->id; - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - for (SymbolRef ref : file->def->outline) { - optional info = - GetSymbolInfo(db, working_files, ref.idx); - if (!info) - continue; - - optional location = - GetLsLocation(db, working_files, ref.loc); - if (!location) - continue; - info->location = *location; - out.result.push_back(*info); - } - - IpcManager::WriteStdout(IpcId::TextDocumentDocumentSymbol, out); - break; - } - - case IpcId::TextDocumentDocumentLink: { - auto msg = message->As(); - - Out_TextDocumentDocumentLink out; - out.id = msg->id; - - if (config->showDocumentLinksOnIncludes) { - QueryFile* file; - if (!FindFileOrFail(db, msg->id, - msg->params.textDocument.uri.GetPath(), &file)) - break; - - WorkingFile* working_file = working_files->GetFileByFilename( - msg->params.textDocument.uri.GetPath()); - if (!working_file) { - LOG_S(INFO) << "Unable to find working file " - << msg->params.textDocument.uri.GetPath(); - break; - } - for (const IndexInclude& include : file->def->includes) { - optional buffer_line; - optional buffer_line_content = - working_file->GetBufferLineContentFromIndexLine(include.line, - &buffer_line); - if (!buffer_line || !buffer_line_content) - continue; - - // Subtract 1 from line because querydb stores 1-based lines but - // vscode expects 0-based lines. - optional between_quotes = - ExtractQuotedRange(*buffer_line - 1, *buffer_line_content); - if (!between_quotes) - continue; - - lsDocumentLink link; - link.target = lsDocumentUri::FromPath(include.resolved_path); - link.range = *between_quotes; - out.result.push_back(link); - } - } - - IpcManager::WriteStdout(IpcId::TextDocumentDocumentLink, out); - break; - } - - case IpcId::TextDocumentCodeAction: { - // NOTE: This code snippet will generate some FixIts for testing: - // - // struct origin { int x, int y }; - // void foo() { - // point origin = { - // x: 0.0, - // y: 0.0 - // }; - // } - // - auto msg = message->As(); - - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file, &file_id)) - break; - - WorkingFile* working_file = working_files->GetFileByFilename( - msg->params.textDocument.uri.GetPath()); - if (!working_file) { - // TODO: send error response. - LOG_S(INFO) - << "[error] textDocument/codeAction could not find working file"; - break; - } - - Out_TextDocumentCodeAction out; - out.id = msg->id; - - // TODO: auto-insert namespace? - - int default_line = (int)working_file->all_buffer_lines.size(); - - // Make sure to call EnsureImplFile before using these. We lazy load - // them because computing the values could involve an entire project - // scan. - optional impl_uri; - optional impl_file_id; - - std::vector syms = - FindSymbolsAtLocation(working_file, file, msg->params.range.start); - for (SymbolRef sym : syms) { - switch (sym.idx.kind) { - case SymbolKind::Type: { - QueryType& type = db->types[sym.idx.idx]; - if (!type.def) - break; - - int num_edits = 0; - - // Get implementation file. - Out_TextDocumentCodeAction::Command command; - - for (QueryFuncId func_id : type.def->funcs) { - QueryFunc& func_def = db->funcs[func_id.id]; - if (!func_def.def || func_def.def->definition_extent) - continue; - - EnsureImplFile(db, file_id, impl_uri /*out*/, - impl_file_id /*out*/); - optional edit = BuildAutoImplementForFunction( - db, working_files, working_file, default_line, file_id, - *impl_file_id, func_def); - if (!edit) - continue; - - ++num_edits; - - // Merge edits together if they are on the same line. - // TODO: be smarter about newline merging? ie, don't end up - // with foo()\n\n\n\nfoo(), we want foo()\n\nfoo()\n\n - // - if (!command.arguments.edits.empty() && - command.arguments.edits[command.arguments.edits.size() - 1] - .range.end.line == edit->range.start.line) { - command.arguments.edits[command.arguments.edits.size() - 1] - .newText += edit->newText; - } else { - command.arguments.edits.push_back(*edit); - } - } - if (command.arguments.edits.empty()) - break; - - // If we're inserting at the end of the document, put a newline - // before the insertion. - if (command.arguments.edits[0].range.start.line >= default_line) - command.arguments.edits[0].newText.insert(0, "\n"); - - command.arguments.textDocumentUri = *impl_uri; - command.title = "Auto-Implement " + std::to_string(num_edits) + - " methods on " + type.def->short_name; - command.command = "cquery._autoImplement"; - out.result.push_back(command); - break; - } - - case SymbolKind::Func: { - QueryFunc& func = db->funcs[sym.idx.idx]; - if (!func.def || func.def->definition_extent) - break; - - EnsureImplFile(db, file_id, impl_uri /*out*/, - impl_file_id /*out*/); - - // Get implementation file. - Out_TextDocumentCodeAction::Command command; - command.title = "Auto-Implement " + func.def->short_name; - command.command = "cquery._autoImplement"; - command.arguments.textDocumentUri = *impl_uri; - optional edit = BuildAutoImplementForFunction( - db, working_files, working_file, default_line, file_id, - *impl_file_id, func); - if (!edit) - break; - - // If we're inserting at the end of the document, put a newline - // before the insertion. - if (edit->range.start.line >= default_line) - edit->newText.insert(0, "\n"); - command.arguments.edits.push_back(*edit); - out.result.push_back(command); - break; - } - default: - break; - } - - // Only show one auto-impl section. - if (!out.result.empty()) - break; - } - - std::vector diagnostics; - working_files->DoAction( - [&]() { diagnostics = working_file->diagnostics_; }); - for (lsDiagnostic& diag : diagnostics) { - if (diag.range.start.line != msg->params.range.start.line) - continue; - - // For error diagnostics, provide an action to resolve an include. - // TODO: find a way to index diagnostic contents so line numbers - // don't get mismatched when actively editing a file. - std::string include_query = - LexWordAroundPos(diag.range.start, working_file->buffer_content); - if (diag.severity == lsDiagnosticSeverity::Error && - !include_query.empty()) { - const size_t kMaxResults = 20; - - std::unordered_set include_absolute_paths; - - // Find include candidate strings. - for (int i = 0; i < db->detailed_names.size(); ++i) { - if (include_absolute_paths.size() > kMaxResults) - break; - if (db->detailed_names[i].find(include_query) == - std::string::npos) - continue; - - optional decl_file_id = - GetDeclarationFileForSymbol(db, db->symbols[i]); - if (!decl_file_id) - continue; - - QueryFile& decl_file = db->files[decl_file_id->id]; - if (!decl_file.def) - continue; - - include_absolute_paths.insert(decl_file.def->path); - } - - // Build include strings. - std::unordered_set include_insert_strings; - include_insert_strings.reserve(include_absolute_paths.size()); - - for (const std::string& path : include_absolute_paths) { - optional item = - include_complete->FindCompletionItemForAbsolutePath(path); - if (!item) - continue; - if (item->textEdit) - include_insert_strings.insert(item->textEdit->newText); - else if (!item->insertText.empty()) - include_insert_strings.insert(item->insertText); - else - assert(false && - "unable to determine insert string for include " - "completion item"); - } - - // Build code action. - if (!include_insert_strings.empty()) { - Out_TextDocumentCodeAction::Command command; - - // Build edits. - for (const std::string& include_insert_string : - include_insert_strings) { - lsTextEdit edit; - optional include_line = FindIncludeLine( - working_file->all_buffer_lines, include_insert_string); - if (!include_line) - continue; - - edit.range.start.line = *include_line; - edit.range.end.line = *include_line; - edit.newText = include_insert_string + "\n"; - command.arguments.edits.push_back(edit); - } - - // Setup metadata and send to client. - if (include_insert_strings.size() == 1) - command.title = "Insert " + *include_insert_strings.begin(); - else - command.title = "Pick one of " + - std::to_string(command.arguments.edits.size()) + - " includes to insert"; - command.command = "cquery._insertInclude"; - command.arguments.textDocumentUri = msg->params.textDocument.uri; - out.result.push_back(command); - } - } - - // clang does not provide accurate enough column reporting for - // diagnostics to do good column filtering, so report all - // diagnostics on the line. - if (!diag.fixits_.empty()) { - Out_TextDocumentCodeAction::Command command; - command.title = "FixIt: " + diag.message; - command.command = "cquery._applyFixIt"; - command.arguments.textDocumentUri = msg->params.textDocument.uri; - command.arguments.edits = diag.fixits_; - out.result.push_back(command); - } - } - - IpcManager::WriteStdout(IpcId::TextDocumentCodeAction, out); - break; - } - - case IpcId::TextDocumentCodeLens: { - auto msg = message->As(); - - Out_TextDocumentCodeLens out; - out.id = msg->id; - - lsDocumentUri file_as_uri = msg->params.textDocument.uri; - std::string path = file_as_uri.GetPath(); - - clang_complete->NotifyView(path); - - QueryFile* file; - if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(), - &file)) - break; - - CommonCodeLensParams common; - common.result = &out.result; - common.db = db; - common.working_files = working_files; - common.working_file = working_files->GetFileByFilename(file->def->path); - - for (SymbolRef ref : file->def->outline) { - // NOTE: We OffsetColumn so that the code lens always show up in a - // predictable order. Otherwise, the client may randomize it. - - SymbolIdx symbol = ref.idx; - switch (symbol.kind) { - case SymbolKind::Type: { - QueryType& type = db->types[symbol.idx]; - if (!type.def) - continue; - AddCodeLens("ref", "refs", &common, ref.loc.OffsetStartColumn(0), - type.uses, type.def->definition_spelling, - true /*force_display*/); - AddCodeLens("derived", "derived", &common, - ref.loc.OffsetStartColumn(1), - ToQueryLocation(db, type.derived), nullopt, - false /*force_display*/); - AddCodeLens("var", "vars", &common, ref.loc.OffsetStartColumn(2), - ToQueryLocation(db, type.instances), nullopt, - false /*force_display*/); - break; - } - case SymbolKind::Func: { - QueryFunc& func = db->funcs[symbol.idx]; - if (!func.def) - continue; - - int16_t offset = 0; - - std::vector base_callers = - GetCallersForAllBaseFunctions(db, func); - std::vector derived_callers = - GetCallersForAllDerivedFunctions(db, func); - if (base_callers.empty() && derived_callers.empty()) { - AddCodeLens("call", "calls", &common, - ref.loc.OffsetStartColumn(offset++), - ToQueryLocation(db, func.callers), nullopt, - true /*force_display*/); - } else { - AddCodeLens("direct call", "direct calls", &common, - ref.loc.OffsetStartColumn(offset++), - ToQueryLocation(db, func.callers), nullopt, - false /*force_display*/); - if (!base_callers.empty()) - AddCodeLens("base call", "base calls", &common, - ref.loc.OffsetStartColumn(offset++), - ToQueryLocation(db, base_callers), nullopt, - false /*force_display*/); - if (!derived_callers.empty()) - AddCodeLens("derived call", "derived calls", &common, - ref.loc.OffsetStartColumn(offset++), - ToQueryLocation(db, derived_callers), nullopt, - false /*force_display*/); - } - - AddCodeLens("derived", "derived", &common, - ref.loc.OffsetStartColumn(offset++), - ToQueryLocation(db, func.derived), nullopt, - false /*force_display*/); - - // "Base" - optional base_loc = - GetBaseDefinitionOrDeclarationSpelling(db, func); - if (base_loc) { - optional ls_base = - GetLsLocation(db, working_files, *base_loc); - if (ls_base) { - optional range = - GetLsRange(common.working_file, ref.loc.range); - if (range) { - TCodeLens code_lens; - code_lens.range = *range; - code_lens.range.start.character += offset++; - code_lens.command = lsCommand(); - code_lens.command->title = "Base"; - code_lens.command->command = "cquery.goto"; - code_lens.command->arguments.uri = ls_base->uri; - code_lens.command->arguments.position = - ls_base->range.start; - out.result.push_back(code_lens); - } - } - } - - break; - } - case SymbolKind::Var: { - QueryVar& var = db->vars[symbol.idx]; - if (!var.def) - continue; - - if (var.def->is_local && !config->codeLensOnLocalVariables) - continue; - - bool force_display = true; - // Do not show 0 refs on macro with no uses, as it is most likely - // a header guard. - if (var.def->is_macro) - force_display = false; - - AddCodeLens("ref", "refs", &common, ref.loc.OffsetStartColumn(0), - var.uses, var.def->definition_spelling, - force_display); - break; - } - case SymbolKind::File: - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - }; - } - - IpcManager::WriteStdout(IpcId::TextDocumentCodeLens, out); - - break; - } - - case IpcId::WorkspaceSymbol: { - // TODO: implement fuzzy search, see - // https://github.com/junegunn/fzf/blob/master/src/matcher.go for - // inspiration - auto msg = message->As(); - - Out_WorkspaceSymbol out; - out.id = msg->id; - - LOG_S(INFO) << "[querydb] Considering " << db->detailed_names.size() - << " candidates for query " << msg->params.query; - - std::string query = msg->params.query; - - std::unordered_set inserted_results; - inserted_results.reserve(config->maxWorkspaceSearchResults); - - for (int i = 0; i < db->detailed_names.size(); ++i) { - if (db->detailed_names[i].find(query) != std::string::npos) { - // Do not show the same entry twice. - if (!inserted_results.insert(db->detailed_names[i]).second) - continue; - - InsertSymbolIntoResult(db, working_files, db->symbols[i], - &out.result); - if (out.result.size() >= config->maxWorkspaceSearchResults) - break; - } - } - - if (out.result.size() < config->maxWorkspaceSearchResults) { - for (int i = 0; i < db->detailed_names.size(); ++i) { - if (SubstringMatch(query, db->detailed_names[i])) { - // Do not show the same entry twice. - if (!inserted_results.insert(db->detailed_names[i]).second) - continue; - - InsertSymbolIntoResult(db, working_files, db->symbols[i], - &out.result); - if (out.result.size() >= config->maxWorkspaceSearchResults) - break; - } - } - } - - LOG_S(INFO) << "[querydb] Found " << out.result.size() - << " results for query " << query; - IpcManager::WriteStdout(IpcId::WorkspaceSymbol, out); - break; - } - - case IpcId::CqueryIndexFile: { - auto msg = message->As(); - queue->index_request.Enqueue( - Index_Request(NormalizePath(msg->params.path), msg->params.args, - msg->params.is_interactive, msg->params.contents)); - break; - } - - case IpcId::CqueryQueryDbWaitForIdleIndexer: { - LOG_S(INFO) << "Waiting for idle"; - int idle_count = 0; - while (true) { - bool has_work = false; - has_work |= import_manager->HasActiveQuerydbImports(); - has_work |= queue->HasWork(); - has_work |= QueryDb_ImportMain(config, db, import_manager, queue, - semantic_cache, working_files); - if (!has_work) - ++idle_count; - else - idle_count = 0; - - // There are race conditions between each of the three checks above, - // so we retry a bunch of times to try to avoid any. - if (idle_count > 10) - break; - } - LOG_S(INFO) << "Done waiting for idle"; - break; - } - - case IpcId::CqueryExitWhenIdle: { - *exit_when_idle = true; - WorkThread::request_exit_on_idle = true; - break; - } - - default: { - LOG_S(FATAL) << "Exiting; unhandled IPC message " - << IpcIdToString(message->method_id); - exit(1); - } + if (message) { + LOG_S(FATAL) << "Exiting; unhandled IPC message " + << IpcIdToString(message->method_id); + exit(1); } } @@ -2629,6 +804,7 @@ void RunQueryDbThread(const std::string& bin_name, handler->file_consumer_shared = &file_consumer_shared; handler->import_manager = &import_manager; handler->timestamp_manager = ×tamp_manager; + handler->semantic_cache = &semantic_cache; handler->working_files = &working_files; handler->clang_complete = &clang_complete; handler->include_complete = &include_complete; @@ -2939,188 +1115,3 @@ int main(int argc, char** argv) { return 0; } - -TEST_SUITE("LexFunctionDeclaration") { - TEST_CASE("simple") { - std::string buffer_content = " void Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "void Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "void Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("ctor") { - std::string buffer_content = " Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "Foo::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("dtor") { - std::string buffer_content = " ~Foo(); "; - lsPosition declaration = CharPos(buffer_content, '~'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "Foo::~Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("complex return type") { - std::string buffer_content = " std::vector Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "std::vector Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "std::vector Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("extra complex return type") { - std::string buffer_content = " std::function < int() > \n Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "std::function < int() > \n Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "std::function < int() > \n Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("parameters") { - std::string buffer_content = "void Foo(int a,\n\n int b); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "void Foo(int a,\n\n int b) {\n}"); - REQUIRE(newlines_after_name == 2); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "void Type::Foo(int a,\n\n int b) {\n}"); - REQUIRE(newlines_after_name == 2); - } -} - -TEST_SUITE("LexWordAroundPos") { - TEST_CASE("edges") { - std::string content = "Foobar"; - REQUIRE(LexWordAroundPos(CharPos(content, 'F'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'o'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'b'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'a'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'r'), content) == "Foobar"); - } - - TEST_CASE("simple") { - std::string content = " Foobar "; - REQUIRE(LexWordAroundPos(CharPos(content, 'F'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'o'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'b'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'a'), content) == "Foobar"); - REQUIRE(LexWordAroundPos(CharPos(content, 'r'), content) == "Foobar"); - } - - TEST_CASE("underscores and numbers") { - std::string content = " _my_t5ype7 "; - REQUIRE(LexWordAroundPos(CharPos(content, '_'), content) == "_my_t5ype7"); - REQUIRE(LexWordAroundPos(CharPos(content, '5'), content) == "_my_t5ype7"); - REQUIRE(LexWordAroundPos(CharPos(content, 'e'), content) == "_my_t5ype7"); - REQUIRE(LexWordAroundPos(CharPos(content, '7'), content) == "_my_t5ype7"); - } - - TEST_CASE("dot, dash, colon are skipped") { - std::string content = "1. 2- 3:"; - REQUIRE(LexWordAroundPos(CharPos(content, '1'), content) == "1"); - REQUIRE(LexWordAroundPos(CharPos(content, '2'), content) == "2"); - REQUIRE(LexWordAroundPos(CharPos(content, '3'), content) == "3"); - } -} - -TEST_SUITE("FindIncludeLine") { - TEST_CASE("in document") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == nullopt); - } - - TEST_CASE("insert before") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 0); - } - - TEST_CASE("insert middle") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 1); - } - - TEST_CASE("insert after") { - std::vector lines = { - "#include ", // 0 - "#include ", // 1 - "", // 2 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 2); - } - - TEST_CASE("ignore header") { - std::vector lines = { - "// FOOBAR", // 0 - "// FOOBAR", // 1 - "// FOOBAR", // 2 - "// FOOBAR", // 3 - "", // 4 - "#include ", // 5 - "#include ", // 6 - "", // 7 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 5); - REQUIRE(FindIncludeLine(lines, "#include ") == 6); - REQUIRE(FindIncludeLine(lines, "#include ") == 7); - } -} diff --git a/src/entry_points.h b/src/entry_points.h index df993711..498a7a02 100644 --- a/src/entry_points.h +++ b/src/entry_points.h @@ -5,6 +5,7 @@ #include "import_manager.h" #include "ipc_manager.h" #include "project.h" +#include "semantic_highlight_symbol_cache.h" #include "threaded_queue.h" #include "timestamp_manager.h" #include "work_thread.h" @@ -12,6 +13,13 @@ // Contains declarations for some of the thread-main functions. +bool QueryDb_ImportMain(Config* config, + QueryDatabase* db, + ImportManager* import_manager, + QueueManager* queue, + SemanticHighlightSymbolCache* semantic_cache, + WorkingFiles* working_files); + WorkThread::Result IndexMain(Config* config, FileConsumer::SharedState* file_consumer_shared, TimestampManager* timestamp_manager, diff --git a/src/lex_utils.cc b/src/lex_utils.cc index 29efbfae..37931f87 100644 --- a/src/lex_utils.cc +++ b/src/lex_utils.cc @@ -265,3 +265,132 @@ TEST_SUITE("Substring") { REQUIRE(!SubstringMatch("ad", "dcba")); } } + +TEST_SUITE("LexFunctionDeclaration") { + TEST_CASE("simple") { + std::string buffer_content = " void Foo(); "; + lsPosition declaration = CharPos(buffer_content, 'F'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, + &newlines_after_name); + REQUIRE(insert_text == "void Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + + LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "void Type::Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + } + + TEST_CASE("ctor") { + std::string buffer_content = " Foo(); "; + lsPosition declaration = CharPos(buffer_content, 'F'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "Foo::Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + } + + TEST_CASE("dtor") { + std::string buffer_content = " ~Foo(); "; + lsPosition declaration = CharPos(buffer_content, '~'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "Foo::~Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + } + + TEST_CASE("complex return type") { + std::string buffer_content = " std::vector Foo(); "; + lsPosition declaration = CharPos(buffer_content, 'F'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, + &newlines_after_name); + REQUIRE(insert_text == "std::vector Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + + LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "std::vector Type::Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + } + + TEST_CASE("extra complex return type") { + std::string buffer_content = " std::function < int() > \n Foo(); "; + lsPosition declaration = CharPos(buffer_content, 'F'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, + &newlines_after_name); + REQUIRE(insert_text == "std::function < int() > \n Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + + LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "std::function < int() > \n Type::Foo() {\n}"); + REQUIRE(newlines_after_name == 0); + } + + TEST_CASE("parameters") { + std::string buffer_content = "void Foo(int a,\n\n int b); "; + lsPosition declaration = CharPos(buffer_content, 'F'); + std::string insert_text; + int newlines_after_name = 0; + + LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, + &newlines_after_name); + REQUIRE(insert_text == "void Foo(int a,\n\n int b) {\n}"); + REQUIRE(newlines_after_name == 2); + + LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), + &insert_text, &newlines_after_name); + REQUIRE(insert_text == "void Type::Foo(int a,\n\n int b) {\n}"); + REQUIRE(newlines_after_name == 2); + } +} + +TEST_SUITE("LexWordAroundPos") { + TEST_CASE("edges") { + std::string content = "Foobar"; + REQUIRE(LexWordAroundPos(CharPos(content, 'F'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'o'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'b'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'a'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'r'), content) == "Foobar"); + } + + TEST_CASE("simple") { + std::string content = " Foobar "; + REQUIRE(LexWordAroundPos(CharPos(content, 'F'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'o'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'b'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'a'), content) == "Foobar"); + REQUIRE(LexWordAroundPos(CharPos(content, 'r'), content) == "Foobar"); + } + + TEST_CASE("underscores and numbers") { + std::string content = " _my_t5ype7 "; + REQUIRE(LexWordAroundPos(CharPos(content, '_'), content) == "_my_t5ype7"); + REQUIRE(LexWordAroundPos(CharPos(content, '5'), content) == "_my_t5ype7"); + REQUIRE(LexWordAroundPos(CharPos(content, 'e'), content) == "_my_t5ype7"); + REQUIRE(LexWordAroundPos(CharPos(content, '7'), content) == "_my_t5ype7"); + } + + TEST_CASE("dot, dash, colon are skipped") { + std::string content = "1. 2- 3:"; + REQUIRE(LexWordAroundPos(CharPos(content, '1'), content) == "1"); + REQUIRE(LexWordAroundPos(CharPos(content, '2'), content) == "2"); + REQUIRE(LexWordAroundPos(CharPos(content, '3'), content) == "3"); + } +} diff --git a/src/lru_cache.h b/src/lru_cache.h index 336ca514..08250a3f 100644 --- a/src/lru_cache.h +++ b/src/lru_cache.h @@ -1,7 +1,9 @@ #pragma once +#include #include #include +#include // Cache that evicts old entries which have not been used recently. Implemented // using array/linear search so this works well for small array sizes. diff --git a/src/message_handler.cc b/src/message_handler.cc index 0dea69f2..ce236d7b 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -1,5 +1,10 @@ #include "message_handler.h" +#include "lex_utils.h" +#include "query_utils.h" + +#include + MessageHandler::MessageHandler() { // Dynamically allocate |message_handlers|, otherwise there will be static // initialization order races. @@ -9,4 +14,205 @@ MessageHandler::MessageHandler() { } // static -std::vector* MessageHandler::message_handlers = nullptr; \ No newline at end of file +std::vector* MessageHandler::message_handlers = nullptr; + +bool FindFileOrFail(QueryDatabase* db, + optional id, + const std::string& absolute_path, + QueryFile** out_query_file, + QueryFileId* out_file_id) { + *out_query_file = nullptr; + + auto it = db->usr_to_file.find(LowerPathIfCaseInsensitive(absolute_path)); + if (it != db->usr_to_file.end()) { + QueryFile& file = db->files[it->second.id]; + if (file.def) { + *out_query_file = &file; + if (out_file_id) + *out_file_id = QueryFileId(it->second.id); + return true; + } + } + + if (out_file_id) + *out_file_id = QueryFileId((size_t)-1); + + LOG_S(INFO) << "Unable to find file \"" << absolute_path << "\""; + + if (id) { + Out_Error out; + out.id = *id; + out.error.code = lsErrorCodes::InternalError; + out.error.message = "Unable to find file " + absolute_path; + IpcManager::WriteStdout(IpcId::Unknown, out); + } + + return false; +} + +void EmitInactiveLines(WorkingFile* working_file, + const std::vector& inactive_regions) { + Out_CquerySetInactiveRegion out; + out.params.uri = lsDocumentUri::FromPath(working_file->filename); + for (Range skipped : inactive_regions) { + optional ls_skipped = GetLsRange(working_file, skipped); + if (ls_skipped) + out.params.inactiveRegions.push_back(*ls_skipped); + } + IpcManager::WriteStdout(IpcId::CqueryPublishInactiveRegions, out); +} + +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: + return Out_CqueryPublishSemanticHighlighting::SymbolType::Type; + case SymbolKind::Func: + return Out_CqueryPublishSemanticHighlighting::SymbolType::Function; + case SymbolKind::Var: + return Out_CqueryPublishSemanticHighlighting::SymbolType::Variable; + default: + assert(false); + return Out_CqueryPublishSemanticHighlighting::SymbolType::Variable; + } + }; + + 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]; + if (!func->def) + continue; // applies to for loop + 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: { + QueryVar* var = &db->vars[sym.idx.idx]; + if (!var->def) + continue; // applies to for loop + 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: + continue; // applies to for loop + } + + optional loc = GetLsRange(working_file, sym.loc.range); + if (loc) { + auto it = grouped_symbols.find(sym.idx); + if (it != grouped_symbols.end()) { + 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.isTypeMember = is_type_member; + symbol.ranges.push_back(*loc); + grouped_symbols[sym.idx] = symbol; + } + } + } + + // Publish. + Out_CqueryPublishSemanticHighlighting out; + out.params.uri = lsDocumentUri::FromPath(working_file->filename); + for (auto& entry : grouped_symbols) + out.params.symbols.push_back(entry.second); + IpcManager::WriteStdout(IpcId::CqueryPublishSemanticHighlighting, out); +} + +void FilterCompletionResponse(Out_TextDocumentComplete* complete_response, + const std::string& complete_text) { +// Used to inject more completions. +#if false + const size_t kNumIterations = 250; + size_t size = complete_response->result.items.size(); + complete_response->result.items.reserve(size * (kNumIterations + 1)); + for (size_t iteration = 0; iteration < kNumIterations; ++iteration) { + for (size_t i = 0; i < size; ++i) { + auto item = complete_response->result.items[i]; + item.label += "#" + std::to_string(iteration); + complete_response->result.items.push_back(item); + } + } +#endif + + const size_t kMaxResultSize = 100u; + if (complete_response->result.items.size() > kMaxResultSize) { + if (complete_text.empty()) { + complete_response->result.items.resize(kMaxResultSize); + } else { + NonElidedVector filtered_result; + filtered_result.reserve(kMaxResultSize); + + std::unordered_set inserted; + inserted.reserve(kMaxResultSize); + + // Find literal matches first. + for (const lsCompletionItem& item : complete_response->result.items) { + if (item.label.find(complete_text) != std::string::npos) { + // Don't insert the same completion entry. + if (!inserted.insert(item.InsertedContent()).second) + continue; + + filtered_result.push_back(item); + if (filtered_result.size() >= kMaxResultSize) + break; + } + } + + // Find fuzzy matches if we haven't found all of the literal matches. + if (filtered_result.size() < kMaxResultSize) { + for (const lsCompletionItem& item : complete_response->result.items) { + if (SubstringMatch(complete_text, item.label)) { + // Don't insert the same completion entry. + if (!inserted.insert(item.InsertedContent()).second) + continue; + + filtered_result.push_back(item); + if (filtered_result.size() >= kMaxResultSize) + break; + } + } + } + + complete_response->result.items = filtered_result; + } + + // Assuming the client does not support out-of-order completion (ie, ao + // matches against oa), our filtering is guaranteed to contain any + // potential matches, so the completion is only incomplete if we have the + // max number of emitted matches. + if (complete_response->result.items.size() >= kMaxResultSize) { + LOG_S(INFO) << "Marking completion results as incomplete"; + complete_response->result.isIncomplete = true; + } + } +} diff --git a/src/message_handler.h b/src/message_handler.h index d1211a41..5dcca231 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -9,6 +9,7 @@ #include "ipc_manager.h" #include "project.h" #include "query.h" +#include "semantic_highlight_symbol_cache.h" #include "threaded_queue.h" #include "timestamp_manager.h" #include "working_files.h" @@ -36,6 +37,7 @@ struct MessageHandler { FileConsumer::SharedState* file_consumer_shared = nullptr; ImportManager* import_manager = nullptr; TimestampManager* timestamp_manager = nullptr; + SemanticHighlightSymbolCache* semantic_cache = nullptr; WorkingFiles* working_files = nullptr; ClangCompleteManager* clang_complete = nullptr; IncludeComplete* include_complete = nullptr; @@ -62,3 +64,23 @@ struct BaseMessageHandler : MessageHandler { Run(message->As()); } }; + +bool FindFileOrFail(QueryDatabase* db, + optional id, + const std::string& absolute_path, + QueryFile** out_query_file, + QueryFileId* out_file_id = nullptr); + +void EmitInactiveLines(WorkingFile* working_file, + const std::vector& inactive_regions); + +void EmitSemanticHighlighting(QueryDatabase* db, + SemanticHighlightSymbolCache* semantic_cache, + WorkingFile* working_file, + QueryFile* file); + +// Pre-filters completion responses before sending to vscode. This results in a +// significantly snappier completion experience as vscode is easily overloaded +// when given 1000+ completion items. +void FilterCompletionResponse(Out_TextDocumentComplete* complete_response, + const std::string& complete_text); \ No newline at end of file diff --git a/src/messages/cquery_base.cc b/src/messages/cquery_base.cc new file mode 100644 index 00000000..56140ab7 --- /dev/null +++ b/src/messages/cquery_base.cc @@ -0,0 +1,42 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryBaseHandler : BaseMessageHandler { + void Run(Ipc_CqueryBase* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_LocationList out; + out.id = request->id; + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Type) { + QueryType& type = db->types[ref.idx.idx]; + if (!type.def) + continue; + std::vector locations = + ToQueryLocation(db, type.def->parents); + out.result = GetLsLocations(db, working_files, locations); + } else if (ref.idx.kind == SymbolKind::Func) { + QueryFunc& func = db->funcs[ref.idx.idx]; + optional location = + GetBaseDefinitionOrDeclarationSpelling(db, func); + if (!location) + continue; + optional ls_loc = + GetLsLocation(db, working_files, *location); + if (!ls_loc) + continue; + out.result.push_back(*ls_loc); + } + } + IpcManager::WriteStdout(IpcId::CqueryBase, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryBaseHandler); \ No newline at end of file diff --git a/src/messages/cquery_call_tree.cc b/src/messages/cquery_call_tree.cc new file mode 100644 index 00000000..0aedf75c --- /dev/null +++ b/src/messages/cquery_call_tree.cc @@ -0,0 +1,46 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryCallTreeInitialHandler + : BaseMessageHandler { + void Run(Ipc_CqueryCallTreeInitial* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_CqueryCallTree out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Func) { + out.result = + BuildInitialCallTree(db, working_files, QueryFuncId(ref.idx.idx)); + break; + } + } + + IpcManager::WriteStdout(IpcId::CqueryCallTreeInitial, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryCallTreeInitialHandler); + +struct CqueryCallTreeExpandHandler + : BaseMessageHandler { + void Run(Ipc_CqueryCallTreeExpand* request) override { + Out_CqueryCallTree out; + out.id = request->id; + + auto func_id = db->usr_to_func.find(request->params.usr); + if (func_id != db->usr_to_func.end()) + out.result = BuildExpandCallTree(db, working_files, func_id->second); + + IpcManager::WriteStdout(IpcId::CqueryCallTreeExpand, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryCallTreeExpandHandler); diff --git a/src/messages/cquery_callers.cc b/src/messages/cquery_callers.cc new file mode 100644 index 00000000..242a9872 --- /dev/null +++ b/src/messages/cquery_callers.cc @@ -0,0 +1,34 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryCallersHandler : BaseMessageHandler { + void Run(Ipc_CqueryCallers* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_LocationList out; + out.id = request->id; + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Func) { + QueryFunc& func = db->funcs[ref.idx.idx]; + std::vector locations = + ToQueryLocation(db, func.callers); + for (QueryFuncRef func_ref : GetCallersForAllBaseFunctions(db, func)) + locations.push_back(func_ref.loc); + for (QueryFuncRef func_ref : GetCallersForAllDerivedFunctions(db, func)) + locations.push_back(func_ref.loc); + + out.result = GetLsLocations(db, working_files, locations); + } + } + IpcManager::WriteStdout(IpcId::CqueryCallers, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryCallersHandler); \ No newline at end of file diff --git a/src/messages/cquery_derived.cc b/src/messages/cquery_derived.cc new file mode 100644 index 00000000..4fce5abe --- /dev/null +++ b/src/messages/cquery_derived.cc @@ -0,0 +1,34 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryDerivedHandler : BaseMessageHandler { + void Run(Ipc_CqueryDerived* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_LocationList out; + out.id = request->id; + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Type) { + QueryType& type = db->types[ref.idx.idx]; + std::vector locations = + ToQueryLocation(db, type.derived); + out.result = GetLsLocations(db, working_files, locations); + } else if (ref.idx.kind == SymbolKind::Func) { + QueryFunc& func = db->funcs[ref.idx.idx]; + std::vector locations = + ToQueryLocation(db, func.derived); + out.result = GetLsLocations(db, working_files, locations); + } + } + IpcManager::WriteStdout(IpcId::CqueryDerived, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryDerivedHandler); \ No newline at end of file diff --git a/src/messages/cquery_did_view.cc b/src/messages/cquery_did_view.cc new file mode 100644 index 00000000..3e141aba --- /dev/null +++ b/src/messages/cquery_did_view.cc @@ -0,0 +1,22 @@ +#include "message_handler.h" + +struct CqueryDidViewHandler + : BaseMessageHandler { + void Run(Ipc_CqueryTextDocumentDidView* request) override { + std::string path = request->params.textDocumentUri.GetPath(); + + WorkingFile* working_file = working_files->GetFileByFilename(path); + if (!working_file) + return; + QueryFile* file = nullptr; + if (!FindFileOrFail(db, nullopt, path, &file)) + return; + + clang_complete->NotifyView(path); + if (file->def) { + EmitInactiveLines(working_file, file->def->inactive_regions); + EmitSemanticHighlighting(db, semantic_cache, working_file, file); + } + } +}; +REGISTER_MESSAGE_HANDLER(CqueryDidViewHandler); diff --git a/src/messages/cquery_exit_when_idle.cc b/src/messages/cquery_exit_when_idle.cc new file mode 100644 index 00000000..0ac93e16 --- /dev/null +++ b/src/messages/cquery_exit_when_idle.cc @@ -0,0 +1,13 @@ +#include "entry_points.h" +#include "message_handler.h" + +#include + +struct CqueryExitWhenIdleHandler : MessageHandler { + IpcId GetId() const override { return IpcId::CqueryExitWhenIdle; } + void Run(std::unique_ptr request) override { + *exit_when_idle = true; + WorkThread::request_exit_on_idle = true; + } +}; +REGISTER_MESSAGE_HANDLER(CqueryExitWhenIdleHandler); \ No newline at end of file diff --git a/src/messages/cquery_freshen_index.cc b/src/messages/cquery_freshen_index.cc new file mode 100644 index 00000000..e1744bdd --- /dev/null +++ b/src/messages/cquery_freshen_index.cc @@ -0,0 +1,44 @@ +#include "message_handler.h" +#include "platform.h" + +#include + +struct CqueryFreshenIndexHandler : MessageHandler { + IpcId GetId() const override { return IpcId::CqueryFreshenIndex; } + + void Run(std::unique_ptr request) { + LOG_S(INFO) << "Freshening " << project->entries.size() << " files"; + + // TODO: think about this flow and test it more. + + // Unmark all files whose timestamp has changed. + CacheLoader cache_loader(config); + for (const auto& file : db->files) { + if (!file.def) + continue; + + optional modification_timestamp = + GetLastModificationTime(file.def->path); + if (!modification_timestamp) + continue; + + optional cached_modification = + timestamp_manager->GetLastCachedModificationTime(&cache_loader, + file.def->path); + if (modification_timestamp != cached_modification) + file_consumer_shared->Reset(file.def->path); + } + + // Send index requests for every file. + project->ForAllFilteredFiles(config, [&](int i, + const Project::Entry& entry) { + LOG_S(INFO) << "[" << i << "/" << (project->entries.size() - 1) + << "] Dispatching index request for file " << entry.filename; + bool is_interactive = + working_files->GetFileByFilename(entry.filename) != nullptr; + queue->index_request.Enqueue( + Index_Request(entry.filename, entry.args, is_interactive, nullopt)); + }); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryFreshenIndexHandler); diff --git a/src/messages/cquery_index_file.cc b/src/messages/cquery_index_file.cc new file mode 100644 index 00000000..1b45ba73 --- /dev/null +++ b/src/messages/cquery_index_file.cc @@ -0,0 +1,11 @@ +#include "message_handler.h" +#include "platform.h" + +struct CqueryIndexFileHandler : BaseMessageHandler { + void Run(Ipc_CqueryIndexFile* request) override { + queue->index_request.Enqueue(Index_Request( + NormalizePath(request->params.path), request->params.args, + request->params.is_interactive, request->params.contents)); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryIndexFileHandler); diff --git a/src/messages/cquery_querydb_wait_for_idle_indexer.cc b/src/messages/cquery_querydb_wait_for_idle_indexer.cc new file mode 100644 index 00000000..73fd6f9a --- /dev/null +++ b/src/messages/cquery_querydb_wait_for_idle_indexer.cc @@ -0,0 +1,32 @@ +#include "entry_points.h" +#include "message_handler.h" + +#include + +struct CqueryQueryDbWaitForIdleIndexerHandler : MessageHandler { + IpcId GetId() const override { + return IpcId::CqueryQueryDbWaitForIdleIndexer; + } + void Run(std::unique_ptr request) override { + LOG_S(INFO) << "Waiting for idle"; + int idle_count = 0; + while (true) { + bool has_work = false; + has_work |= import_manager->HasActiveQuerydbImports(); + has_work |= queue->HasWork(); + has_work |= QueryDb_ImportMain(config, db, import_manager, queue, + semantic_cache, working_files); + if (!has_work) + ++idle_count; + else + idle_count = 0; + + // There are race conditions between each of the three checks above, + // so we retry a bunch of times to try to avoid any. + if (idle_count > 10) + break; + } + LOG_S(INFO) << "Done waiting for idle"; + } +}; +REGISTER_MESSAGE_HANDLER(CqueryQueryDbWaitForIdleIndexerHandler); \ No newline at end of file diff --git a/src/messages/cquery_type_hierarchy_tree.cc b/src/messages/cquery_type_hierarchy_tree.cc new file mode 100644 index 00000000..12283c26 --- /dev/null +++ b/src/messages/cquery_type_hierarchy_tree.cc @@ -0,0 +1,35 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryTypeHierarchyTreeHandler + : BaseMessageHandler { + void Run(Ipc_CqueryTypeHierarchyTree* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) + return; + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_CqueryTypeHierarchyTree out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Type) { + out.result = BuildInheritanceHierarchyForType(db, working_files, + QueryTypeId(ref.idx.idx)); + break; + } + if (ref.idx.kind == SymbolKind::Func) { + out.result = BuildInheritanceHierarchyForFunc(db, working_files, + QueryFuncId(ref.idx.idx)); + break; + } + } + + IpcManager::WriteStdout(IpcId::CqueryTypeHierarchyTree, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryTypeHierarchyTreeHandler); \ No newline at end of file diff --git a/src/messages/cquery_vars.cc b/src/messages/cquery_vars.cc new file mode 100644 index 00000000..82f06637 --- /dev/null +++ b/src/messages/cquery_vars.cc @@ -0,0 +1,29 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct CqueryVarsHandler : BaseMessageHandler { + void Run(Ipc_CqueryVars* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_LocationList out; + out.id = request->id; + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + if (ref.idx.kind == SymbolKind::Type) { + QueryType& type = db->types[ref.idx.idx]; + std::vector locations = + ToQueryLocation(db, type.instances); + out.result = GetLsLocations(db, working_files, locations); + } + } + IpcManager::WriteStdout(IpcId::CqueryVars, out); + } +}; +REGISTER_MESSAGE_HANDLER(CqueryVarsHandler); \ No newline at end of file diff --git a/src/messages/text_document_code_action.cc b/src/messages/text_document_code_action.cc new file mode 100644 index 00000000..9eabd6cc --- /dev/null +++ b/src/messages/text_document_code_action.cc @@ -0,0 +1,546 @@ +#include "lex_utils.h" +#include "message_handler.h" +#include "query_utils.h" + +#include +#include + +namespace { + +optional FindIncludeLine(const std::vector& lines, + const std::string& full_include_line) { + // + // This returns an include line. For example, + // + // #include // 0 + // #include // 1 + // + // Given #include , this will return '1', which means that the + // #include text should be inserted at the start of line 1. Inserting + // at the start of a line allows insertion at both the top and bottom of the + // document. + // + // If the include line is already in the document this returns nullopt. + // + + optional last_include_line; + optional best_include_line; + + // 1 => include line is gt content (ie, it should go after) + // -1 => include line is lt content (ie, it should go before) + int last_line_compare = 1; + + for (int line = 0; line < (int)lines.size(); ++line) { + if (!StartsWith(lines[line], "#include")) { + last_line_compare = 1; + continue; + } + + last_include_line = line; + + int current_line_compare = full_include_line.compare(lines[line]); + if (current_line_compare == 0) + return nullopt; + + if (last_line_compare == 1 && current_line_compare == -1) + best_include_line = line; + last_line_compare = current_line_compare; + } + + if (best_include_line) + return *best_include_line; + // If |best_include_line| didn't match that means we likely didn't find an + // include which was lt the new one, so put it at the end of the last include + // list. + if (last_include_line) + return *last_include_line + 1; + // No includes, use top of document. + return 0; +} + +optional GetImplementationFile(QueryDatabase* db, + QueryFileId file_id, + QueryFile* file) { + for (SymbolRef sym : file->def->outline) { + switch (sym.idx.kind) { + case SymbolKind::Func: { + QueryFunc& func = db->funcs[sym.idx.idx]; + // Note: we ignore the definition if it is in the same file (ie, + // possibly a header). + if (func.def && func.def->definition_extent && + func.def->definition_extent->path != file_id) { + return func.def->definition_extent->path; + } + break; + } + case SymbolKind::Var: { + QueryVar& var = db->vars[sym.idx.idx]; + // Note: we ignore the definition if it is in the same file (ie, + // possibly a header). + if (var.def && var.def->definition_extent && + var.def->definition_extent->path != file_id) { + return db->vars[sym.idx.idx].def->definition_extent->path; + } + break; + } + default: + break; + } + } + + // No associated definition, scan the project for a file in the same + // directory with the same base-name. + std::string original_path = LowerPathIfCaseInsensitive(file->def->path); + std::string target_path = original_path; + size_t last = target_path.find_last_of('.'); + if (last != std::string::npos) { + target_path = target_path.substr(0, last); + } + + LOG_S(INFO) << "!! Looking for impl file that starts with " << target_path; + + for (auto& entry : db->usr_to_file) { + Usr path = entry.first; + + // Do not consider header files for implementation files. + // TODO: make file extensions configurable. + if (EndsWith(path, ".h") || EndsWith(path, ".hpp")) + continue; + + if (StartsWith(path, target_path) && path != original_path) { + return entry.second; + } + } + + return nullopt; +} + +void EnsureImplFile(QueryDatabase* db, + QueryFileId file_id, + optional& impl_uri, + optional& impl_file_id) { + if (!impl_uri.has_value()) { + QueryFile& file = db->files[file_id.id]; + assert(file.def); + + impl_file_id = GetImplementationFile(db, file_id, &file); + if (!impl_file_id.has_value()) + impl_file_id = file_id; + + QueryFile& impl_file = db->files[impl_file_id->id]; + if (impl_file.def) + impl_uri = lsDocumentUri::FromPath(impl_file.def->path); + else + impl_uri = lsDocumentUri::FromPath(file.def->path); + } +} + +optional BuildAutoImplementForFunction(QueryDatabase* db, + WorkingFiles* working_files, + WorkingFile* working_file, + int default_line, + QueryFileId decl_file_id, + QueryFileId impl_file_id, + QueryFunc& func) { + assert(func.def); + for (const QueryLocation& decl : func.declarations) { + if (decl.path != decl_file_id) + continue; + + optional ls_decl = GetLsRange(working_file, decl.range); + if (!ls_decl) + continue; + + optional type_name; + optional same_file_insert_end; + if (func.def->declaring_type) { + QueryType& declaring_type = db->types[func.def->declaring_type->id]; + if (declaring_type.def) { + type_name = declaring_type.def->short_name; + optional ls_type_def_extent = GetLsRange( + working_file, declaring_type.def->definition_extent->range); + if (ls_type_def_extent) { + same_file_insert_end = ls_type_def_extent->end; + same_file_insert_end->character += 1; // move past semicolon. + } + } + } + + std::string insert_text; + int newlines_after_name = 0; + LexFunctionDeclaration(working_file->buffer_content, ls_decl->start, + type_name, &insert_text, &newlines_after_name); + + if (!same_file_insert_end) { + same_file_insert_end = ls_decl->end; + same_file_insert_end->line += newlines_after_name; + same_file_insert_end->character = 1000; + } + + lsTextEdit edit; + + if (decl_file_id == impl_file_id) { + edit.range.start = *same_file_insert_end; + edit.range.end = *same_file_insert_end; + edit.newText = "\n\n" + insert_text; + } else { + lsPosition best_pos; + best_pos.line = default_line; + int best_dist = INT_MAX; + + QueryFile& file = db->files[impl_file_id.id]; + assert(file.def); + for (SymbolRef sym : file.def->outline) { + switch (sym.idx.kind) { + case SymbolKind::Func: { + QueryFunc& sym_func = db->funcs[sym.idx.idx]; + if (!sym_func.def || !sym_func.def->definition_extent) + break; + + for (QueryLocation& func_decl : sym_func.declarations) { + if (func_decl.path == decl_file_id) { + int dist = func_decl.range.start.line - decl.range.start.line; + if (abs(dist) < abs(best_dist)) { + optional def_loc = GetLsLocation( + db, working_files, *sym_func.def->definition_extent); + if (!def_loc) + continue; + + best_dist = dist; + + if (dist > 0) + best_pos = def_loc->range.start; + else + best_pos = def_loc->range.end; + } + } + } + + break; + } + case SymbolKind::Var: { + // TODO: handle vars. + break; + } + case SymbolKind::Invalid: + case SymbolKind::File: + case SymbolKind::Type: + LOG_S(WARNING) << "Unexpected SymbolKind " + << static_cast(sym.idx.kind); + break; + } + } + + edit.range.start = best_pos; + edit.range.end = best_pos; + if (best_dist < 0) + edit.newText = "\n\n" + insert_text; + else + edit.newText = insert_text + "\n\n"; + } + + return edit; + } + + return nullopt; +} + +} // namespace + +struct TextDocumentCodeActionHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentCodeAction* request) override { + // NOTE: This code snippet will generate some FixIts for testing: + // + // struct origin { int x, int y }; + // void foo() { + // point origin = { + // x: 0.0, + // y: 0.0 + // }; + // } + // + + QueryFileId file_id; + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file, + &file_id)) { + return; + } + + WorkingFile* working_file = working_files->GetFileByFilename( + request->params.textDocument.uri.GetPath()); + if (!working_file) { + // TODO: send error response. + LOG_S(WARNING) + << "[error] textDocument/codeAction could not find working file"; + return; + } + + Out_TextDocumentCodeAction out; + out.id = request->id; + + // TODO: auto-insert namespace? + + int default_line = (int)working_file->all_buffer_lines.size(); + + // Make sure to call EnsureImplFile before using these. We lazy load + // them because computing the values could involve an entire project + // scan. + optional impl_uri; + optional impl_file_id; + + std::vector syms = + FindSymbolsAtLocation(working_file, file, request->params.range.start); + for (SymbolRef sym : syms) { + switch (sym.idx.kind) { + case SymbolKind::Type: { + QueryType& type = db->types[sym.idx.idx]; + if (!type.def) + break; + + int num_edits = 0; + + // Get implementation file. + Out_TextDocumentCodeAction::Command command; + + for (QueryFuncId func_id : type.def->funcs) { + QueryFunc& func_def = db->funcs[func_id.id]; + if (!func_def.def || func_def.def->definition_extent) + continue; + + EnsureImplFile(db, file_id, impl_uri /*out*/, impl_file_id /*out*/); + optional edit = BuildAutoImplementForFunction( + db, working_files, working_file, default_line, file_id, + *impl_file_id, func_def); + if (!edit) + continue; + + ++num_edits; + + // Merge edits together if they are on the same line. + // TODO: be smarter about newline merging? ie, don't end up + // with foo()\n\n\n\nfoo(), we want foo()\n\nfoo()\n\n + // + if (!command.arguments.edits.empty() && + command.arguments.edits[command.arguments.edits.size() - 1] + .range.end.line == edit->range.start.line) { + command.arguments.edits[command.arguments.edits.size() - 1] + .newText += edit->newText; + } else { + command.arguments.edits.push_back(*edit); + } + } + if (command.arguments.edits.empty()) + break; + + // If we're inserting at the end of the document, put a newline + // before the insertion. + if (command.arguments.edits[0].range.start.line >= default_line) + command.arguments.edits[0].newText.insert(0, "\n"); + + command.arguments.textDocumentUri = *impl_uri; + command.title = "Auto-Implement " + std::to_string(num_edits) + + " methods on " + type.def->short_name; + command.command = "cquery._autoImplement"; + out.result.push_back(command); + break; + } + + case SymbolKind::Func: { + QueryFunc& func = db->funcs[sym.idx.idx]; + if (!func.def || func.def->definition_extent) + break; + + EnsureImplFile(db, file_id, impl_uri /*out*/, impl_file_id /*out*/); + + // Get implementation file. + Out_TextDocumentCodeAction::Command command; + command.title = "Auto-Implement " + func.def->short_name; + command.command = "cquery._autoImplement"; + command.arguments.textDocumentUri = *impl_uri; + optional edit = BuildAutoImplementForFunction( + db, working_files, working_file, default_line, file_id, + *impl_file_id, func); + if (!edit) + break; + + // If we're inserting at the end of the document, put a newline + // before the insertion. + if (edit->range.start.line >= default_line) + edit->newText.insert(0, "\n"); + command.arguments.edits.push_back(*edit); + out.result.push_back(command); + break; + } + default: + break; + } + + // Only show one auto-impl section. + if (!out.result.empty()) + break; + } + + std::vector diagnostics; + working_files->DoAction( + [&]() { diagnostics = working_file->diagnostics_; }); + for (lsDiagnostic& diag : diagnostics) { + if (diag.range.start.line != request->params.range.start.line) + continue; + + // For error diagnostics, provide an action to resolve an include. + // TODO: find a way to index diagnostic contents so line numbers + // don't get mismatched when actively editing a file. + std::string include_query = + LexWordAroundPos(diag.range.start, working_file->buffer_content); + if (diag.severity == lsDiagnosticSeverity::Error && + !include_query.empty()) { + const size_t kMaxResults = 20; + + std::unordered_set include_absolute_paths; + + // Find include candidate strings. + for (int i = 0; i < db->detailed_names.size(); ++i) { + if (include_absolute_paths.size() > kMaxResults) + break; + if (db->detailed_names[i].find(include_query) == std::string::npos) + continue; + + optional decl_file_id = + GetDeclarationFileForSymbol(db, db->symbols[i]); + if (!decl_file_id) + continue; + + QueryFile& decl_file = db->files[decl_file_id->id]; + if (!decl_file.def) + continue; + + include_absolute_paths.insert(decl_file.def->path); + } + + // Build include strings. + std::unordered_set include_insert_strings; + include_insert_strings.reserve(include_absolute_paths.size()); + + for (const std::string& path : include_absolute_paths) { + optional item = + include_complete->FindCompletionItemForAbsolutePath(path); + if (!item) + continue; + if (item->textEdit) + include_insert_strings.insert(item->textEdit->newText); + else if (!item->insertText.empty()) + include_insert_strings.insert(item->insertText); + else + assert(false && + "unable to determine insert string for include " + "completion item"); + } + + // Build code action. + if (!include_insert_strings.empty()) { + Out_TextDocumentCodeAction::Command command; + + // Build edits. + for (const std::string& include_insert_string : + include_insert_strings) { + lsTextEdit edit; + optional include_line = FindIncludeLine( + working_file->all_buffer_lines, include_insert_string); + if (!include_line) + continue; + + edit.range.start.line = *include_line; + edit.range.end.line = *include_line; + edit.newText = include_insert_string + "\n"; + command.arguments.edits.push_back(edit); + } + + // Setup metadata and send to client. + if (include_insert_strings.size() == 1) + command.title = "Insert " + *include_insert_strings.begin(); + else + command.title = "Pick one of " + + std::to_string(command.arguments.edits.size()) + + " includes to insert"; + command.command = "cquery._insertInclude"; + command.arguments.textDocumentUri = request->params.textDocument.uri; + out.result.push_back(command); + } + } + + // clang does not provide accurate enough column reporting for + // diagnostics to do good column filtering, so report all + // diagnostics on the line. + if (!diag.fixits_.empty()) { + Out_TextDocumentCodeAction::Command command; + command.title = "FixIt: " + diag.message; + command.command = "cquery._applyFixIt"; + command.arguments.textDocumentUri = request->params.textDocument.uri; + command.arguments.edits = diag.fixits_; + out.result.push_back(command); + } + } + + IpcManager::WriteStdout(IpcId::TextDocumentCodeAction, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentCodeActionHandler); + +TEST_SUITE("FindIncludeLine") { + TEST_CASE("in document") { + std::vector lines = { + "#include ", // 0 + "#include " // 1 + }; + + REQUIRE(FindIncludeLine(lines, "#include ") == nullopt); + } + + TEST_CASE("insert before") { + std::vector lines = { + "#include ", // 0 + "#include " // 1 + }; + + REQUIRE(FindIncludeLine(lines, "#include ") == 0); + } + + TEST_CASE("insert middle") { + std::vector lines = { + "#include ", // 0 + "#include " // 1 + }; + + REQUIRE(FindIncludeLine(lines, "#include ") == 1); + } + + TEST_CASE("insert after") { + std::vector lines = { + "#include ", // 0 + "#include ", // 1 + "", // 2 + }; + + REQUIRE(FindIncludeLine(lines, "#include ") == 2); + } + + TEST_CASE("ignore header") { + std::vector lines = { + "// FOOBAR", // 0 + "// FOOBAR", // 1 + "// FOOBAR", // 2 + "// FOOBAR", // 3 + "", // 4 + "#include ", // 5 + "#include ", // 6 + "", // 7 + }; + + REQUIRE(FindIncludeLine(lines, "#include ") == 5); + REQUIRE(FindIncludeLine(lines, "#include ") == 6); + REQUIRE(FindIncludeLine(lines, "#include ") == 7); + } +} diff --git a/src/messages/text_document_code_lens.cc b/src/messages/text_document_code_lens.cc new file mode 100644 index 00000000..84c3ab77 --- /dev/null +++ b/src/messages/text_document_code_lens.cc @@ -0,0 +1,141 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct TextDocumentCodeLensHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentCodeLens* request) override { + Out_TextDocumentCodeLens out; + out.id = request->id; + + lsDocumentUri file_as_uri = request->params.textDocument.uri; + std::string path = file_as_uri.GetPath(); + + clang_complete->NotifyView(path); + + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + CommonCodeLensParams common; + common.result = &out.result; + common.db = db; + common.working_files = working_files; + common.working_file = working_files->GetFileByFilename(file->def->path); + + for (SymbolRef ref : file->def->outline) { + // NOTE: We OffsetColumn so that the code lens always show up in a + // predictable order. Otherwise, the client may randomize it. + + SymbolIdx symbol = ref.idx; + switch (symbol.kind) { + case SymbolKind::Type: { + QueryType& type = db->types[symbol.idx]; + if (!type.def) + continue; + AddCodeLens("ref", "refs", &common, ref.loc.OffsetStartColumn(0), + type.uses, type.def->definition_spelling, + true /*force_display*/); + AddCodeLens("derived", "derived", &common, + ref.loc.OffsetStartColumn(1), + ToQueryLocation(db, type.derived), nullopt, + false /*force_display*/); + AddCodeLens("var", "vars", &common, ref.loc.OffsetStartColumn(2), + ToQueryLocation(db, type.instances), nullopt, + false /*force_display*/); + break; + } + case SymbolKind::Func: { + QueryFunc& func = db->funcs[symbol.idx]; + if (!func.def) + continue; + + int16_t offset = 0; + + std::vector base_callers = + GetCallersForAllBaseFunctions(db, func); + std::vector derived_callers = + GetCallersForAllDerivedFunctions(db, func); + if (base_callers.empty() && derived_callers.empty()) { + AddCodeLens("call", "calls", &common, + ref.loc.OffsetStartColumn(offset++), + ToQueryLocation(db, func.callers), nullopt, + true /*force_display*/); + } else { + AddCodeLens("direct call", "direct calls", &common, + ref.loc.OffsetStartColumn(offset++), + ToQueryLocation(db, func.callers), nullopt, + false /*force_display*/); + if (!base_callers.empty()) + AddCodeLens("base call", "base calls", &common, + ref.loc.OffsetStartColumn(offset++), + ToQueryLocation(db, base_callers), nullopt, + false /*force_display*/); + if (!derived_callers.empty()) + AddCodeLens("derived call", "derived calls", &common, + ref.loc.OffsetStartColumn(offset++), + ToQueryLocation(db, derived_callers), nullopt, + false /*force_display*/); + } + + AddCodeLens("derived", "derived", &common, + ref.loc.OffsetStartColumn(offset++), + ToQueryLocation(db, func.derived), nullopt, + false /*force_display*/); + + // "Base" + optional base_loc = + GetBaseDefinitionOrDeclarationSpelling(db, func); + if (base_loc) { + optional ls_base = + GetLsLocation(db, working_files, *base_loc); + if (ls_base) { + optional range = + GetLsRange(common.working_file, ref.loc.range); + if (range) { + TCodeLens code_lens; + code_lens.range = *range; + code_lens.range.start.character += offset++; + code_lens.command = lsCommand(); + code_lens.command->title = "Base"; + code_lens.command->command = "cquery.goto"; + code_lens.command->arguments.uri = ls_base->uri; + code_lens.command->arguments.position = ls_base->range.start; + out.result.push_back(code_lens); + } + } + } + + break; + } + case SymbolKind::Var: { + QueryVar& var = db->vars[symbol.idx]; + if (!var.def) + continue; + + if (var.def->is_local && !config->codeLensOnLocalVariables) + continue; + + bool force_display = true; + // Do not show 0 refs on macro with no uses, as it is most likely + // a header guard. + if (var.def->is_macro) + force_display = false; + + AddCodeLens("ref", "refs", &common, ref.loc.OffsetStartColumn(0), + var.uses, var.def->definition_spelling, force_display); + break; + } + case SymbolKind::File: + case SymbolKind::Invalid: { + assert(false && "unexpected"); + break; + } + }; + } + + IpcManager::WriteStdout(IpcId::TextDocumentCodeLens, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentCodeLensHandler); diff --git a/src/messages/text_document_completion.cc b/src/messages/text_document_completion.cc new file mode 100644 index 00000000..6d4cf290 --- /dev/null +++ b/src/messages/text_document_completion.cc @@ -0,0 +1,124 @@ +#include "message_handler.h" + +#include "lex_utils.h" + +struct TextDocumentCompletionHandler : MessageHandler { + IpcId GetId() const override { return IpcId::TextDocumentCompletion; } + + void Run(std::unique_ptr message) override { + auto request = std::shared_ptr( + static_cast(message.release())); + + std::string path = request->params.textDocument.uri.GetPath(); + WorkingFile* file = working_files->GetFileByFilename(path); + + // It shouldn't be possible, but sometimes vscode will send queries out + // of order, ie, we get completion request before buffer content update. + std::string buffer_line; + if (request->params.position.line >= 0 && + request->params.position.line < file->all_buffer_lines.size()) { + buffer_line = file->all_buffer_lines[request->params.position.line]; + } + + if (ShouldRunIncludeCompletion(buffer_line)) { + Out_TextDocumentComplete out; + out.id = request->id; + + { + std::unique_lock lock( + include_complete->completion_items_mutex, std::defer_lock); + if (include_complete->is_scanning) + lock.lock(); + out.result.items.assign(include_complete->completion_items.begin(), + include_complete->completion_items.end()); + if (lock) + lock.unlock(); + + // Update textEdit params. + for (lsCompletionItem& item : out.result.items) { + item.textEdit->range.start.line = request->params.position.line; + item.textEdit->range.start.character = 0; + item.textEdit->range.end.line = request->params.position.line; + item.textEdit->range.end.character = (int)buffer_line.size(); + } + } + + FilterCompletionResponse(&out, buffer_line); + IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out); + } else { + bool is_global_completion = false; + std::string existing_completion; + if (file) { + request->params.position = file->FindStableCompletionSource( + request->params.position, &is_global_completion, + &existing_completion); + } + + ClangCompleteManager::OnComplete callback = std::bind( + [this, is_global_completion, existing_completion, request]( + const NonElidedVector& results, + bool is_cached_result) { + Out_TextDocumentComplete out; + out.id = request->id; + out.result.items = results; + + // Emit completion results. + FilterCompletionResponse(&out, existing_completion); + IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out); + + // Cache completion results. + if (!is_cached_result) { + std::string path = request->params.textDocument.uri.GetPath(); + if (is_global_completion) { + global_code_complete_cache->WithLock([&]() { + global_code_complete_cache->cached_path_ = path; + global_code_complete_cache->cached_results_ = results; + }); + } else { + non_global_code_complete_cache->WithLock([&]() { + non_global_code_complete_cache->cached_path_ = path; + non_global_code_complete_cache->cached_completion_position_ = + request->params.position; + non_global_code_complete_cache->cached_results_ = results; + }); + } + } + }, + std::placeholders::_1, std::placeholders::_2); + + bool is_cache_match = false; + global_code_complete_cache->WithLock([&]() { + is_cache_match = is_global_completion && + global_code_complete_cache->cached_path_ == path && + !global_code_complete_cache->cached_results_.empty(); + }); + if (is_cache_match) { + ClangCompleteManager::OnComplete freshen_global = + [this](NonElidedVector results, + bool is_cached_result) { + assert(!is_cached_result); + + // note: path is updated in the normal completion handler. + global_code_complete_cache->WithLock([&]() { + global_code_complete_cache->cached_results_ = results; + }); + }; + + global_code_complete_cache->WithLock([&]() { + callback(global_code_complete_cache->cached_results_, + true /*is_cached_result*/); + }); + clang_complete->CodeComplete(request->params, freshen_global); + } else if (non_global_code_complete_cache->IsCacheValid( + request->params)) { + non_global_code_complete_cache->WithLock([&]() { + callback(non_global_code_complete_cache->cached_results_, + true /*is_cached_result*/); + }); + } else { + clang_complete->CodeComplete(request->params, callback); + } + } + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentCompletionHandler); diff --git a/src/messages/text_document_definition.cc b/src/messages/text_document_definition.cc new file mode 100644 index 00000000..a5e03686 --- /dev/null +++ b/src/messages/text_document_definition.cc @@ -0,0 +1,97 @@ +#include "message_handler.h" +#include "query_utils.h" + +namespace { +void PushBack(NonElidedVector* result, + optional location) { + if (location) + result->push_back(*location); +} +} // namespace + +struct TextDocumentDefinitionHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDefinition* request) override { + QueryFileId file_id; + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file, + &file_id)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_TextDocumentDefinition out; + out.id = request->id; + + int target_line = request->params.position.line + 1; + int target_column = request->params.position.character + 1; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + // Found symbol. Return definition. + + // Special cases which are handled: + // - symbol has declaration but no definition (ie, pure virtual) + // - start at spelling but end at extent for better mouse tooltip + // - goto declaration while in definition of recursive type + + optional def_loc = + GetDefinitionSpellingOfSymbol(db, ref.idx); + + // We use spelling start and extent end because this causes vscode to + // highlight the entire definition when previewing / hoving with the + // mouse. + optional def_extent = + GetDefinitionExtentOfSymbol(db, ref.idx); + if (def_loc && def_extent) + def_loc->range.end = def_extent->range.end; + + // If the cursor is currently at or in the definition we should goto + // the declaration if possible. We also want to use declarations if + // we're pointing to, ie, a pure virtual function which has no + // definition. + if (!def_loc || (def_loc->path == file_id && + def_loc->range.Contains(target_line, target_column))) { + // Goto declaration. + + std::vector declarations = + GetDeclarationsOfSymbolForGotoDefinition(db, ref.idx); + for (auto declaration : declarations) { + optional ls_declaration = + GetLsLocation(db, working_files, declaration); + if (ls_declaration) + out.result.push_back(*ls_declaration); + } + // We found some declarations. Break so we don't add the definition + // location. + if (!out.result.empty()) + break; + } + + if (def_loc) { + PushBack(&out.result, GetLsLocation(db, working_files, *def_loc)); + } + + if (!out.result.empty()) + break; + } + + // No symbols - check for includes. + if (out.result.empty()) { + for (const IndexInclude& include : file->def->includes) { + if (include.line == target_line) { + lsLocation result; + result.uri = lsDocumentUri::FromPath(include.resolved_path); + out.result.push_back(result); + break; + } + } + } + + IpcManager::WriteStdout(IpcId::TextDocumentDefinition, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDefinitionHandler); diff --git a/src/messages/text_document_did_change.cc b/src/messages/text_document_did_change.cc new file mode 100644 index 00000000..5f6b647c --- /dev/null +++ b/src/messages/text_document_did_change.cc @@ -0,0 +1,13 @@ +#include "message_handler.h" + +struct TextDocumentDidChangeHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDidChange* request) override { + std::string path = request->params.textDocument.uri.GetPath(); + working_files->OnChange(request->params); + clang_complete->NotifyEdit(path); + clang_complete->DiagnosticsUpdate( + request->params.textDocument.AsTextDocumentIdentifier()); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDidChangeHandler); diff --git a/src/messages/text_document_did_close.cc b/src/messages/text_document_did_close.cc new file mode 100644 index 00000000..4ead135f --- /dev/null +++ b/src/messages/text_document_did_close.cc @@ -0,0 +1,18 @@ +#include "message_handler.h" + +struct TextDocumentDidCloseHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDidClose* request) override { + std::string path = request->params.textDocument.uri.GetPath(); + + // Clear any diagnostics for the file. + Out_TextDocumentPublishDiagnostics out; + out.params.uri = request->params.textDocument.uri; + IpcManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); + + // Remove internal state. + working_files->OnClose(request->params); + clang_complete->NotifyClose(path); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDidCloseHandler); diff --git a/src/messages/text_document_did_open.cc b/src/messages/text_document_did_open.cc new file mode 100644 index 00000000..d931026c --- /dev/null +++ b/src/messages/text_document_did_open.cc @@ -0,0 +1,41 @@ +#include "cache.h" +#include "message_handler.h" +#include "timer.h" + +struct TextDocumentDidOpenHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDidOpen* request) override { + // NOTE: This function blocks code lens. If it starts taking a long time + // we will need to find a way to unblock the code lens request. + + Timer time; + std::string path = request->params.textDocument.uri.GetPath(); + WorkingFile* working_file = working_files->OnOpen(request->params); + optional cached_file_contents = + LoadCachedFileContents(config, path); + if (cached_file_contents) + working_file->SetIndexContent(*cached_file_contents); + else + working_file->SetIndexContent(working_file->buffer_content); + + QueryFile* file = nullptr; + FindFileOrFail(db, nullopt, path, &file); + if (file && file->def) { + EmitInactiveLines(working_file, file->def->inactive_regions); + EmitSemanticHighlighting(db, semantic_cache, working_file, file); + } + + time.ResetAndPrint( + "[querydb] Loading cached index file for DidOpen (blocks " + "CodeLens)"); + + include_complete->AddFile(working_file->filename); + clang_complete->NotifyView(path); + + // Submit new index request. + const Project::Entry& entry = project->FindCompilationEntryForFile(path); + queue->index_request.PriorityEnqueue(Index_Request( + entry.filename, entry.args, true /*is_interactive*/, nullopt)); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDidOpenHandler); diff --git a/src/messages/text_document_did_save.cc b/src/messages/text_document_did_save.cc new file mode 100644 index 00000000..13f4ca88 --- /dev/null +++ b/src/messages/text_document_did_save.cc @@ -0,0 +1,27 @@ +#include "message_handler.h" + +struct TextDocumentDidSaveHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDidSave* request) override { + std::string path = request->params.textDocument.uri.GetPath(); + // Send out an index request, and copy the current buffer state so we + // can update the cached index contents when the index is done. + // + // We also do not index if there is already an index request. + // + // TODO: Cancel outgoing index request. Might be tricky to make + // efficient since we have to cancel. + // - we could have an |atomic active_cancellations| variable + // that all of the indexers check before accepting an index. if + // zero we don't slow down fast-path. if non-zero we acquire + // mutex and check to see if we should skip the current request. + // if so, ignore that index response. + // TODO: send as priority request + Project::Entry entry = project->FindCompilationEntryForFile(path); + queue->index_request.Enqueue(Index_Request( + entry.filename, entry.args, true /*is_interactive*/, nullopt)); + + clang_complete->NotifySave(path); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDidSaveHandler); diff --git a/src/messages/text_document_document_link.cc b/src/messages/text_document_document_link.cc new file mode 100644 index 00000000..f6d780df --- /dev/null +++ b/src/messages/text_document_document_link.cc @@ -0,0 +1,51 @@ +#include "lex_utils.h" +#include "message_handler.h" + +#include + +struct TextDocumentDocumentLinkHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDocumentLink* request) override { + Out_TextDocumentDocumentLink out; + out.id = request->id; + + if (config->showDocumentLinksOnIncludes) { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = working_files->GetFileByFilename( + request->params.textDocument.uri.GetPath()); + if (!working_file) { + LOG_S(WARNING) << "Unable to find working file " + << request->params.textDocument.uri.GetPath(); + return; + } + for (const IndexInclude& include : file->def->includes) { + optional buffer_line; + optional buffer_line_content = + working_file->GetBufferLineContentFromIndexLine(include.line, + &buffer_line); + if (!buffer_line || !buffer_line_content) + continue; + + // Subtract 1 from line because querydb stores 1-based lines but + // vscode expects 0-based lines. + optional between_quotes = + ExtractQuotedRange(*buffer_line - 1, *buffer_line_content); + if (!between_quotes) + continue; + + lsDocumentLink link; + link.target = lsDocumentUri::FromPath(include.resolved_path); + link.range = *between_quotes; + out.result.push_back(link); + } + } + + IpcManager::WriteStdout(IpcId::TextDocumentDocumentLink, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDocumentLinkHandler); diff --git a/src/messages/text_document_document_symbol.cc b/src/messages/text_document_document_symbol.cc new file mode 100644 index 00000000..a324cf31 --- /dev/null +++ b/src/messages/text_document_document_symbol.cc @@ -0,0 +1,32 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct TextDocumentDocumentSymbolHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDocumentSymbol* request) override { + Out_TextDocumentDocumentSymbol out; + out.id = request->id; + + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + for (SymbolRef ref : file->def->outline) { + optional info = + GetSymbolInfo(db, working_files, ref.idx); + if (!info) + continue; + + optional location = GetLsLocation(db, working_files, ref.loc); + if (!location) + continue; + info->location = *location; + out.result.push_back(*info); + } + + IpcManager::WriteStdout(IpcId::TextDocumentDocumentSymbol, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDocumentSymbolHandler); diff --git a/src/messages/text_document_highlight.cc b/src/messages/text_document_highlight.cc new file mode 100644 index 00000000..51a0179a --- /dev/null +++ b/src/messages/text_document_highlight.cc @@ -0,0 +1,46 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct TextDocumentDocumentHighlightHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentDocumentHighlight* request) override { + QueryFileId file_id; + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file, + &file_id)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_TextDocumentDocumentHighlight out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + // Found symbol. Return references to highlight. + std::vector uses = GetUsesOfSymbol(db, ref.idx); + out.result.reserve(uses.size()); + for (const QueryLocation& use : uses) { + if (use.path != file_id) + continue; + + optional ls_location = + GetLsLocation(db, working_files, use); + if (!ls_location) + continue; + + lsDocumentHighlight highlight; + highlight.kind = lsDocumentHighlightKind::Text; + highlight.range = ls_location->range; + out.result.push_back(highlight); + } + break; + } + + IpcManager::WriteStdout(IpcId::TextDocumentDocumentHighlight, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentDocumentHighlightHandler); diff --git a/src/messages/text_document_hover.cc b/src/messages/text_document_hover.cc new file mode 100644 index 00000000..23896a3b --- /dev/null +++ b/src/messages/text_document_hover.cc @@ -0,0 +1,36 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct TextDocumentHoverHandler : BaseMessageHandler { + void Run(Ipc_TextDocumentHover* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_TextDocumentHover out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + // Found symbol. Return hover. + optional ls_range = GetLsRange( + working_files->GetFileByFilename(file->def->path), ref.loc.range); + if (!ls_range) + continue; + + out.result.contents.value = GetHoverForSymbol(db, ref.idx); + out.result.contents.language = file->def->language; + + out.result.range = *ls_range; + break; + } + + IpcManager::WriteStdout(IpcId::TextDocumentHover, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentHoverHandler); diff --git a/src/messages/text_document_references.cc b/src/messages/text_document_references.cc new file mode 100644 index 00000000..bd7911ad --- /dev/null +++ b/src/messages/text_document_references.cc @@ -0,0 +1,47 @@ +#include "message_handler.h" +#include "query_utils.h" + +#include + +struct TextDocumentReferencesHandler + : BaseMessageHandler { + void Run(Ipc_TextDocumentReferences* request) override { + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_TextDocumentReferences out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + optional excluded_declaration; + if (!request->params.context.includeDeclaration) { + LOG_S(INFO) << "Excluding declaration in references"; + excluded_declaration = GetDefinitionSpellingOfSymbol(db, ref.idx); + } + + // Found symbol. Return references. + std::vector uses = GetUsesOfSymbol(db, ref.idx); + out.result.reserve(uses.size()); + for (const QueryLocation& use : uses) { + if (excluded_declaration.has_value() && use == *excluded_declaration) + continue; + + optional ls_location = + GetLsLocation(db, working_files, use); + if (ls_location) + out.result.push_back(*ls_location); + } + break; + } + + IpcManager::WriteStdout(IpcId::TextDocumentReferences, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentReferencesHandler); diff --git a/src/messages/text_document_rename.cc b/src/messages/text_document_rename.cc new file mode 100644 index 00000000..5504b1df --- /dev/null +++ b/src/messages/text_document_rename.cc @@ -0,0 +1,32 @@ +#include "message_handler.h" +#include "query_utils.h" + +struct TextDocumentRenameHandler : BaseMessageHandler { + void Run(Ipc_TextDocumentRename* request) override { + QueryFileId file_id; + QueryFile* file; + if (!FindFileOrFail(db, request->id, + request->params.textDocument.uri.GetPath(), &file, + &file_id)) { + return; + } + + WorkingFile* working_file = + working_files->GetFileByFilename(file->def->path); + + Out_TextDocumentRename out; + out.id = request->id; + + for (const SymbolRef& ref : + FindSymbolsAtLocation(working_file, file, request->params.position)) { + // Found symbol. Return references to rename. + std::vector uses = GetUsesOfSymbol(db, ref.idx); + out.result = + BuildWorkspaceEdit(db, working_files, uses, request->params.newName); + break; + } + + IpcManager::WriteStdout(IpcId::TextDocumentRename, out); + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentRenameHandler); diff --git a/src/messages/text_document_signature_help.cc b/src/messages/text_document_signature_help.cc new file mode 100644 index 00000000..df6ad629 --- /dev/null +++ b/src/messages/text_document_signature_help.cc @@ -0,0 +1,86 @@ +#include "message_handler.h" +#include "timer.h" + +struct TextDocumentSignatureHelpHandler : MessageHandler { + IpcId GetId() const override { return IpcId::TextDocumentSignatureHelp; } + + void Run(std::unique_ptr message) override { + auto request = message->As(); + lsTextDocumentPositionParams& params = request->params; + WorkingFile* file = + working_files->GetFileByFilename(params.textDocument.uri.GetPath()); + std::string search; + int active_param = 0; + if (file) { + lsPosition completion_position; + search = file->FindClosestCallNameInBuffer(params.position, &active_param, + &completion_position); + params.position = completion_position; + } + if (search.empty()) + return; + + ClangCompleteManager::OnComplete callback = std::bind( + [this](BaseIpcMessage* message, std::string search, int active_param, + const NonElidedVector& results, + bool is_cached_result) { + auto msg = message->As(); + + Out_TextDocumentSignatureHelp out; + out.id = msg->id; + + for (auto& result : results) { + if (result.label != search) + continue; + + lsSignatureInformation signature; + signature.label = result.detail; + for (auto& parameter : result.parameters_) { + lsParameterInformation ls_param; + ls_param.label = parameter; + signature.parameters.push_back(ls_param); + } + out.result.signatures.push_back(signature); + } + + // Guess the signature the user wants based on available parameter + // count. + out.result.activeSignature = 0; + for (size_t i = 0; i < out.result.signatures.size(); ++i) { + if (active_param < out.result.signatures.size()) { + out.result.activeSignature = (int)i; + break; + } + } + + // Set signature to what we parsed from the working file. + out.result.activeParameter = active_param; + + Timer timer; + IpcManager::WriteStdout(IpcId::TextDocumentSignatureHelp, out); + + if (!is_cached_result) { + signature_cache->WithLock([&]() { + signature_cache->cached_path_ = + msg->params.textDocument.uri.GetPath(); + signature_cache->cached_completion_position_ = + msg->params.position; + signature_cache->cached_results_ = results; + }); + } + + delete message; + }, + message.release(), search, active_param, std::placeholders::_1, + std::placeholders::_2); + + if (signature_cache->IsCacheValid(params)) { + signature_cache->WithLock([&]() { + callback(signature_cache->cached_results_, true /*is_cached_result*/); + }); + } else { + clang_complete->CodeComplete(params, std::move(callback)); + } + } +}; +REGISTER_MESSAGE_HANDLER(TextDocumentSignatureHelpHandler); diff --git a/src/messages/workspace_symbol.cc b/src/messages/workspace_symbol.cc new file mode 100644 index 00000000..5f5c6729 --- /dev/null +++ b/src/messages/workspace_symbol.cc @@ -0,0 +1,56 @@ +#include "lex_utils.h" +#include "message_handler.h" +#include "query_utils.h" + +#include + +struct WorkspaceSymbolHandler : BaseMessageHandler { + void Run(Ipc_WorkspaceSymbol* request) override { + // TODO: implement fuzzy search, see + // https://github.com/junegunn/fzf/blob/master/src/matcher.go for + // inspiration + + Out_WorkspaceSymbol out; + out.id = request->id; + + LOG_S(INFO) << "[querydb] Considering " << db->detailed_names.size() + << " candidates for query " << request->params.query; + + std::string query = request->params.query; + + std::unordered_set inserted_results; + inserted_results.reserve(config->maxWorkspaceSearchResults); + + for (int i = 0; i < db->detailed_names.size(); ++i) { + if (db->detailed_names[i].find(query) != std::string::npos) { + // Do not show the same entry twice. + if (!inserted_results.insert(db->detailed_names[i]).second) + continue; + + InsertSymbolIntoResult(db, working_files, db->symbols[i], &out.result); + if (out.result.size() >= config->maxWorkspaceSearchResults) + break; + } + } + + if (out.result.size() < config->maxWorkspaceSearchResults) { + for (int i = 0; i < db->detailed_names.size(); ++i) { + if (SubstringMatch(query, db->detailed_names[i])) { + // Do not show the same entry twice. + if (!inserted_results.insert(db->detailed_names[i]).second) + continue; + + InsertSymbolIntoResult(db, working_files, db->symbols[i], + &out.result); + if (out.result.size() >= config->maxWorkspaceSearchResults) + break; + } + } + } + + LOG_S(INFO) << "[querydb] Found " << out.result.size() + << " results for query " << query; + IpcManager::WriteStdout(IpcId::WorkspaceSymbol, out); + } +}; +REGISTER_MESSAGE_HANDLER(WorkspaceSymbolHandler); diff --git a/src/query.cc b/src/query.cc index c7ccfbe6..e6c74a67 100644 --- a/src/query.cc +++ b/src/query.cc @@ -618,7 +618,7 @@ IndexUpdate::IndexUpdate(const IdMap& previous_id_map, } void IndexUpdate::Merge(const IndexUpdate& update) { - // This function runs on an indexer thread. +// This function runs on an indexer thread. #define INDEX_UPDATE_APPEND(name) AddRange(&name, update.name); #define INDEX_UPDATE_MERGE(name) AddMergeableRange(&name, update.name); diff --git a/src/semantic_highlight_symbol_cache.cc b/src/semantic_highlight_symbol_cache.cc new file mode 100644 index 00000000..cb60f7ef --- /dev/null +++ b/src/semantic_highlight_symbol_cache.cc @@ -0,0 +1,37 @@ +#include "semantic_highlight_symbol_cache.h" + +SemanticHighlightSymbolCache::Entry::Entry(const std::string& path) + : path(path) {} + +int SemanticHighlightSymbolCache::Entry::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(); +} + +SemanticHighlightSymbolCache::SemanticHighlightSymbolCache() + : cache_(kCacheSize) {} + +std::shared_ptr +SemanticHighlightSymbolCache::GetCacheForFile(const std::string& path) { + return cache_.Get(path, [&]() { return std::make_shared(path); }); +} diff --git a/src/semantic_highlight_symbol_cache.h b/src/semantic_highlight_symbol_cache.h new file mode 100644 index 00000000..987f7fc3 --- /dev/null +++ b/src/semantic_highlight_symbol_cache.h @@ -0,0 +1,32 @@ +#pragma once + +#include "lru_cache.h" +#include "query.h" + +#include +#include + +// 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); + + int GetStableId(SymbolKind kind, const std::string& detailed_name); + }; + + constexpr static int kCacheSize = 10; + LruCache cache_; + + SemanticHighlightSymbolCache(); + + std::shared_ptr GetCacheForFile(const std::string& path); +};