mirror of
				https://github.com/MaskRay/ccls.git
				synced 2025-10-31 12:42:34 +00:00 
			
		
		
		
	Finish migrating to messages/
This commit is contained in:
		
							parent
							
								
									2bd4671ddd
								
							
						
					
					
						commit
						b441a90e0d
					
				
							
								
								
									
										2019
									
								
								src/command_line.cc
									
									
									
									
									
								
							
							
						
						
									
										2019
									
								
								src/command_line.cc
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -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, | ||||
|  | ||||
							
								
								
									
										129
									
								
								src/lex_utils.cc
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								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<int> 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<int> Foo() {\n}"); | ||||
|     REQUIRE(newlines_after_name == 0); | ||||
| 
 | ||||
|     LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), | ||||
|                            &insert_text, &newlines_after_name); | ||||
|     REQUIRE(insert_text == "std::vector<int> 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"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <cassert> | ||||
| #include <limits> | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| // Cache that evicts old entries which have not been used recently. Implemented
 | ||||
| // using array/linear search so this works well for small array sizes.
 | ||||
|  | ||||
| @ -1,5 +1,10 @@ | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| #include "lex_utils.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| MessageHandler::MessageHandler() { | ||||
|   // Dynamically allocate |message_handlers|, otherwise there will be static
 | ||||
|   // initialization order races.
 | ||||
| @ -9,4 +14,205 @@ MessageHandler::MessageHandler() { | ||||
| } | ||||
| 
 | ||||
| // static
 | ||||
| std::vector<MessageHandler*>* MessageHandler::message_handlers = nullptr; | ||||
| std::vector<MessageHandler*>* MessageHandler::message_handlers = nullptr; | ||||
| 
 | ||||
| bool FindFileOrFail(QueryDatabase* db, | ||||
|                     optional<lsRequestId> 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<Range>& inactive_regions) { | ||||
|   Out_CquerySetInactiveRegion out; | ||||
|   out.params.uri = lsDocumentUri::FromPath(working_file->filename); | ||||
|   for (Range skipped : inactive_regions) { | ||||
|     optional<lsRange> 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<SymbolIdx, Out_CqueryPublishSemanticHighlighting::Symbol> | ||||
|       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<lsRange> 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<lsCompletionItem> filtered_result; | ||||
|       filtered_result.reserve(kMaxResultSize); | ||||
| 
 | ||||
|       std::unordered_set<std::string> 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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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<TMessage>()); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| bool FindFileOrFail(QueryDatabase* db, | ||||
|                     optional<lsRequestId> id, | ||||
|                     const std::string& absolute_path, | ||||
|                     QueryFile** out_query_file, | ||||
|                     QueryFileId* out_file_id = nullptr); | ||||
| 
 | ||||
| void EmitInactiveLines(WorkingFile* working_file, | ||||
|                        const std::vector<Range>& 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); | ||||
							
								
								
									
										42
									
								
								src/messages/cquery_base.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/messages/cquery_base.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryBaseHandler : BaseMessageHandler<Ipc_CqueryBase> { | ||||
|   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<QueryLocation> 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<QueryLocation> location = | ||||
|             GetBaseDefinitionOrDeclarationSpelling(db, func); | ||||
|         if (!location) | ||||
|           continue; | ||||
|         optional<lsLocation> 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); | ||||
							
								
								
									
										46
									
								
								src/messages/cquery_call_tree.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/messages/cquery_call_tree.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryCallTreeInitialHandler | ||||
|     : BaseMessageHandler<Ipc_CqueryCallTreeInitial> { | ||||
|   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<Ipc_CqueryCallTreeExpand> { | ||||
|   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); | ||||
							
								
								
									
										34
									
								
								src/messages/cquery_callers.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/messages/cquery_callers.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryCallersHandler : BaseMessageHandler<Ipc_CqueryCallers> { | ||||
|   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<QueryLocation> 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); | ||||
							
								
								
									
										34
									
								
								src/messages/cquery_derived.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/messages/cquery_derived.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryDerivedHandler : BaseMessageHandler<Ipc_CqueryDerived> { | ||||
|   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<QueryLocation> 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<QueryLocation> locations = | ||||
|             ToQueryLocation(db, func.derived); | ||||
|         out.result = GetLsLocations(db, working_files, locations); | ||||
|       } | ||||
|     } | ||||
|     IpcManager::WriteStdout(IpcId::CqueryDerived, out); | ||||
|   } | ||||
| }; | ||||
| REGISTER_MESSAGE_HANDLER(CqueryDerivedHandler); | ||||
							
								
								
									
										22
									
								
								src/messages/cquery_did_view.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/messages/cquery_did_view.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| struct CqueryDidViewHandler | ||||
|     : BaseMessageHandler<Ipc_CqueryTextDocumentDidView> { | ||||
|   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); | ||||
							
								
								
									
										13
									
								
								src/messages/cquery_exit_when_idle.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/messages/cquery_exit_when_idle.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #include "entry_points.h" | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct CqueryExitWhenIdleHandler : MessageHandler { | ||||
|   IpcId GetId() const override { return IpcId::CqueryExitWhenIdle; } | ||||
|   void Run(std::unique_ptr<BaseIpcMessage> request) override { | ||||
|     *exit_when_idle = true; | ||||
|     WorkThread::request_exit_on_idle = true; | ||||
|   } | ||||
| }; | ||||
| REGISTER_MESSAGE_HANDLER(CqueryExitWhenIdleHandler); | ||||
							
								
								
									
										44
									
								
								src/messages/cquery_freshen_index.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/messages/cquery_freshen_index.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| #include "message_handler.h" | ||||
| #include "platform.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct CqueryFreshenIndexHandler : MessageHandler { | ||||
|   IpcId GetId() const override { return IpcId::CqueryFreshenIndex; } | ||||
| 
 | ||||
|   void Run(std::unique_ptr<BaseIpcMessage> 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<int64_t> modification_timestamp = | ||||
|           GetLastModificationTime(file.def->path); | ||||
|       if (!modification_timestamp) | ||||
|         continue; | ||||
| 
 | ||||
|       optional<int64_t> 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); | ||||
							
								
								
									
										11
									
								
								src/messages/cquery_index_file.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/messages/cquery_index_file.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| #include "message_handler.h" | ||||
| #include "platform.h" | ||||
| 
 | ||||
| struct CqueryIndexFileHandler : BaseMessageHandler<Ipc_CqueryIndexFile> { | ||||
|   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); | ||||
							
								
								
									
										32
									
								
								src/messages/cquery_querydb_wait_for_idle_indexer.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/messages/cquery_querydb_wait_for_idle_indexer.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| #include "entry_points.h" | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct CqueryQueryDbWaitForIdleIndexerHandler : MessageHandler { | ||||
|   IpcId GetId() const override { | ||||
|     return IpcId::CqueryQueryDbWaitForIdleIndexer; | ||||
|   } | ||||
|   void Run(std::unique_ptr<BaseIpcMessage> 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); | ||||
							
								
								
									
										35
									
								
								src/messages/cquery_type_hierarchy_tree.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/messages/cquery_type_hierarchy_tree.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryTypeHierarchyTreeHandler | ||||
|     : BaseMessageHandler<Ipc_CqueryTypeHierarchyTree> { | ||||
|   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); | ||||
							
								
								
									
										29
									
								
								src/messages/cquery_vars.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/messages/cquery_vars.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct CqueryVarsHandler : BaseMessageHandler<Ipc_CqueryVars> { | ||||
|   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<QueryLocation> locations = | ||||
|             ToQueryLocation(db, type.instances); | ||||
|         out.result = GetLsLocations(db, working_files, locations); | ||||
|       } | ||||
|     } | ||||
|     IpcManager::WriteStdout(IpcId::CqueryVars, out); | ||||
|   } | ||||
| }; | ||||
| REGISTER_MESSAGE_HANDLER(CqueryVarsHandler); | ||||
							
								
								
									
										546
									
								
								src/messages/text_document_code_action.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										546
									
								
								src/messages/text_document_code_action.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,546 @@ | ||||
| #include "lex_utils.h" | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| #include <doctest/doctest.h> | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| optional<int> FindIncludeLine(const std::vector<std::string>& lines, | ||||
|                               const std::string& full_include_line) { | ||||
|   //
 | ||||
|   // This returns an include line. For example,
 | ||||
|   //
 | ||||
|   //    #include <a>  // 0
 | ||||
|   //    #include <c>  // 1
 | ||||
|   //
 | ||||
|   // Given #include <b>, this will return '1', which means that the
 | ||||
|   // #include <b> 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<int> last_include_line; | ||||
|   optional<int> 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<QueryFileId> 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<lsDocumentUri>& impl_uri, | ||||
|                     optional<QueryFileId>& 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<lsTextEdit> 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<lsRange> ls_decl = GetLsRange(working_file, decl.range); | ||||
|     if (!ls_decl) | ||||
|       continue; | ||||
| 
 | ||||
|     optional<std::string> type_name; | ||||
|     optional<lsPosition> 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<lsRange> 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<lsLocation> 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<int>(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<Ipc_TextDocumentCodeAction> { | ||||
|   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<lsDocumentUri> impl_uri; | ||||
|     optional<QueryFileId> impl_file_id; | ||||
| 
 | ||||
|     std::vector<SymbolRef> 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<lsTextEdit> 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<lsTextEdit> 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<lsDiagnostic> 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<std::string> 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<QueryFileId> 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<std::string> include_insert_strings; | ||||
|         include_insert_strings.reserve(include_absolute_paths.size()); | ||||
| 
 | ||||
|         for (const std::string& path : include_absolute_paths) { | ||||
|           optional<lsCompletionItem> 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<int> 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<std::string> lines = { | ||||
|         "#include <bbb>",  // 0
 | ||||
|         "#include <ddd>"   // 1
 | ||||
|     }; | ||||
| 
 | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <bbb>") == nullopt); | ||||
|   } | ||||
| 
 | ||||
|   TEST_CASE("insert before") { | ||||
|     std::vector<std::string> lines = { | ||||
|         "#include <bbb>",  // 0
 | ||||
|         "#include <ddd>"   // 1
 | ||||
|     }; | ||||
| 
 | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <aaa>") == 0); | ||||
|   } | ||||
| 
 | ||||
|   TEST_CASE("insert middle") { | ||||
|     std::vector<std::string> lines = { | ||||
|         "#include <bbb>",  // 0
 | ||||
|         "#include <ddd>"   // 1
 | ||||
|     }; | ||||
| 
 | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <ccc>") == 1); | ||||
|   } | ||||
| 
 | ||||
|   TEST_CASE("insert after") { | ||||
|     std::vector<std::string> lines = { | ||||
|         "#include <bbb>",  // 0
 | ||||
|         "#include <ddd>",  // 1
 | ||||
|         "",                // 2
 | ||||
|     }; | ||||
| 
 | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <eee>") == 2); | ||||
|   } | ||||
| 
 | ||||
|   TEST_CASE("ignore header") { | ||||
|     std::vector<std::string> lines = { | ||||
|         "// FOOBAR",       // 0
 | ||||
|         "// FOOBAR",       // 1
 | ||||
|         "// FOOBAR",       // 2
 | ||||
|         "// FOOBAR",       // 3
 | ||||
|         "",                // 4
 | ||||
|         "#include <bbb>",  // 5
 | ||||
|         "#include <ddd>",  // 6
 | ||||
|         "",                // 7
 | ||||
|     }; | ||||
| 
 | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <a>") == 5); | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <c>") == 6); | ||||
|     REQUIRE(FindIncludeLine(lines, "#include <e>") == 7); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										141
									
								
								src/messages/text_document_code_lens.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								src/messages/text_document_code_lens.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,141 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct TextDocumentCodeLensHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentCodeLens> { | ||||
|   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<QueryFuncRef> base_callers = | ||||
|               GetCallersForAllBaseFunctions(db, func); | ||||
|           std::vector<QueryFuncRef> 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<QueryLocation> base_loc = | ||||
|               GetBaseDefinitionOrDeclarationSpelling(db, func); | ||||
|           if (base_loc) { | ||||
|             optional<lsLocation> ls_base = | ||||
|                 GetLsLocation(db, working_files, *base_loc); | ||||
|             if (ls_base) { | ||||
|               optional<lsRange> 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<lsCodeLensCommandArguments>(); | ||||
|                 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); | ||||
							
								
								
									
										124
									
								
								src/messages/text_document_completion.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/messages/text_document_completion.cc
									
									
									
									
									
										Normal file
									
								
							| @ -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<BaseIpcMessage> message) override { | ||||
|     auto request = std::shared_ptr<Ipc_TextDocumentComplete>( | ||||
|         static_cast<Ipc_TextDocumentComplete*>(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<std::mutex> 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<lsCompletionItem>& 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<lsCompletionItem> 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); | ||||
							
								
								
									
										97
									
								
								src/messages/text_document_definition.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/messages/text_document_definition.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| namespace { | ||||
| void PushBack(NonElidedVector<lsLocation>* result, | ||||
|               optional<lsLocation> location) { | ||||
|   if (location) | ||||
|     result->push_back(*location); | ||||
| } | ||||
| }  // namespace
 | ||||
| 
 | ||||
| struct TextDocumentDefinitionHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDefinition> { | ||||
|   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<QueryLocation> 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<QueryLocation> 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<QueryLocation> declarations = | ||||
|             GetDeclarationsOfSymbolForGotoDefinition(db, ref.idx); | ||||
|         for (auto declaration : declarations) { | ||||
|           optional<lsLocation> 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); | ||||
							
								
								
									
										13
									
								
								src/messages/text_document_did_change.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/messages/text_document_did_change.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| struct TextDocumentDidChangeHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDidChange> { | ||||
|   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); | ||||
							
								
								
									
										18
									
								
								src/messages/text_document_did_close.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/messages/text_document_did_close.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| struct TextDocumentDidCloseHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDidClose> { | ||||
|   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); | ||||
							
								
								
									
										41
									
								
								src/messages/text_document_did_open.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/messages/text_document_did_open.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | ||||
| #include "cache.h" | ||||
| #include "message_handler.h" | ||||
| #include "timer.h" | ||||
| 
 | ||||
| struct TextDocumentDidOpenHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDidOpen> { | ||||
|   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<std::string> 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); | ||||
							
								
								
									
										27
									
								
								src/messages/text_document_did_save.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/messages/text_document_did_save.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| struct TextDocumentDidSaveHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDidSave> { | ||||
|   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<int> 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); | ||||
							
								
								
									
										51
									
								
								src/messages/text_document_document_link.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/messages/text_document_document_link.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| #include "lex_utils.h" | ||||
| #include "message_handler.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct TextDocumentDocumentLinkHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDocumentLink> { | ||||
|   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<int> buffer_line; | ||||
|         optional<std::string> 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<lsRange> 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); | ||||
							
								
								
									
										32
									
								
								src/messages/text_document_document_symbol.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/messages/text_document_document_symbol.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct TextDocumentDocumentSymbolHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDocumentSymbol> { | ||||
|   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<lsSymbolInformation> info = | ||||
|           GetSymbolInfo(db, working_files, ref.idx); | ||||
|       if (!info) | ||||
|         continue; | ||||
| 
 | ||||
|       optional<lsLocation> 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); | ||||
							
								
								
									
										46
									
								
								src/messages/text_document_highlight.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/messages/text_document_highlight.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct TextDocumentDocumentHighlightHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentDocumentHighlight> { | ||||
|   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<QueryLocation> uses = GetUsesOfSymbol(db, ref.idx); | ||||
|       out.result.reserve(uses.size()); | ||||
|       for (const QueryLocation& use : uses) { | ||||
|         if (use.path != file_id) | ||||
|           continue; | ||||
| 
 | ||||
|         optional<lsLocation> 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); | ||||
							
								
								
									
										36
									
								
								src/messages/text_document_hover.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/messages/text_document_hover.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct TextDocumentHoverHandler : BaseMessageHandler<Ipc_TextDocumentHover> { | ||||
|   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<lsRange> 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); | ||||
							
								
								
									
										47
									
								
								src/messages/text_document_references.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/messages/text_document_references.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct TextDocumentReferencesHandler | ||||
|     : BaseMessageHandler<Ipc_TextDocumentReferences> { | ||||
|   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<QueryLocation> 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<QueryLocation> 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<lsLocation> 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); | ||||
							
								
								
									
										32
									
								
								src/messages/text_document_rename.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/messages/text_document_rename.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| struct TextDocumentRenameHandler : BaseMessageHandler<Ipc_TextDocumentRename> { | ||||
|   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<QueryLocation> 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); | ||||
							
								
								
									
										86
									
								
								src/messages/text_document_signature_help.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/messages/text_document_signature_help.cc
									
									
									
									
									
										Normal file
									
								
							| @ -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<BaseIpcMessage> message) override { | ||||
|     auto request = message->As<Ipc_TextDocumentSignatureHelp>(); | ||||
|     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<lsCompletionItem>& results, | ||||
|                bool is_cached_result) { | ||||
|           auto msg = message->As<Ipc_TextDocumentSignatureHelp>(); | ||||
| 
 | ||||
|           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); | ||||
							
								
								
									
										56
									
								
								src/messages/workspace_symbol.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/messages/workspace_symbol.cc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| #include "lex_utils.h" | ||||
| #include "message_handler.h" | ||||
| #include "query_utils.h" | ||||
| 
 | ||||
| #include <loguru.hpp> | ||||
| 
 | ||||
| struct WorkspaceSymbolHandler : BaseMessageHandler<Ipc_WorkspaceSymbol> { | ||||
|   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<std::string> 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); | ||||
| @ -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); | ||||
|  | ||||
							
								
								
									
										37
									
								
								src/semantic_highlight_symbol_cache.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/semantic_highlight_symbol_cache.cc
									
									
									
									
									
										Normal file
									
								
							| @ -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::Entry> | ||||
| SemanticHighlightSymbolCache::GetCacheForFile(const std::string& path) { | ||||
|   return cache_.Get(path, [&]() { return std::make_shared<Entry>(path); }); | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/semantic_highlight_symbol_cache.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/semantic_highlight_symbol_cache.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "lru_cache.h" | ||||
| #include "query.h" | ||||
| 
 | ||||
| #include <string> | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| // 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<std::string, int>; | ||||
|     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<std::string, Entry> cache_; | ||||
| 
 | ||||
|   SemanticHighlightSymbolCache(); | ||||
| 
 | ||||
|   std::shared_ptr<Entry> GetCacheForFile(const std::string& path); | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user