diff --git a/README.md b/README.md index d5641a18..de7edcb0 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ be productive with cquery. Here's a list of implemented features: * hover * diagnostics * code actions (clang FixIts) + * darken/fade code disabled by preprocessor # Setup - build cquery, install extension, setup project diff --git a/src/clang_utils.cc b/src/clang_utils.cc index bc731ba3..ce30557b 100644 --- a/src/clang_utils.cc +++ b/src/clang_utils.cc @@ -65,8 +65,6 @@ optional BuildDiagnostic(CXDiagnostic diagnostic) { // Report fixits unsigned num_fixits = clang_getDiagnosticNumFixIts(diagnostic); - if (num_fixits > 0) - std::cerr << "!!!! Got " << num_fixits << " fixits" << std::endl; for (unsigned i = 0; i < num_fixits; ++i) { CXSourceRange replacement_range; CXString text = clang_getDiagnosticFixIt(diagnostic, i, &replacement_range); diff --git a/src/command_line.cc b/src/command_line.cc index f0638fd3..1bc06ccd 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -167,6 +167,7 @@ IpcManager* IpcManager::instance_ = nullptr; bool ShouldDisplayIpcTiming(IpcId id) { switch (id) { case IpcId::TextDocumentPublishDiagnostics: + case IpcId::CqueryPublishInactiveRegions: return false; default: return true; @@ -757,6 +758,16 @@ std::vector FindSymbolsAtLocation(WorkingFile* working_file, QueryFil +void PublishInactiveLines(WorkingFile* working_file, const std::vector& inactive) { + Out_CquerySetInactiveRegion out; + out.params.uri = lsDocumentUri::FromPath(working_file->filename); + for (Range skipped : inactive) { + optional ls_skipped = GetLsRange(working_file, skipped); + if (ls_skipped) + out.params.inactiveRegions.push_back(*ls_skipped); + } + IpcManager::instance()->SendOutMessageToClient(IpcId::CqueryPublishInactiveRegions, out); +} @@ -1104,6 +1115,7 @@ void ParseFile(IndexerConfig* config, // |is_interactive| will be true, or they can change the flags cquery runs // with, in which case vscode will get restarted. if (is_interactive || !new_index->diagnostics.empty()) { + // Emit diagnostics. Out_TextDocumentPublishDiagnostics diag; diag.params.uri = lsDocumentUri::FromPath(new_index->path); diag.params.diagnostics = new_index->diagnostics; @@ -1111,8 +1123,20 @@ void ParseFile(IndexerConfig* config, // Cache diagnostics so we can show fixits. WorkingFile* working_file = working_files->GetFileByFilename(new_index->path); - if (working_file) + if (working_file) { working_file->diagnostics = new_index->diagnostics; + + // Publish source ranges disabled by preprocessor. + if (is_interactive) { + // TODO: We shouldn't be updating actual indexed content here, but we + // need to use the latest indexed content for the remapping. + // TODO: We should also remap diagnostics. + if (indexed_content) + working_file->SetIndexContent(*indexed_content); + + PublishInactiveLines(working_file, new_index->skipped_by_preprocessor); + } + } } @@ -1666,7 +1690,12 @@ bool QueryDbMainLoop( working_file->SetIndexContent(*cached_file_contents); else working_file->SetIndexContent(working_file->buffer_content); - time.ResetAndPrint("[querydb] Loading cached index file for DidOpen"); + + std::unique_ptr cache = LoadCachedIndex(config, msg->params.textDocument.uri.GetPath()); + if (cache && !cache->skipped_by_preprocessor.empty()) + PublishInactiveLines(working_file, cache->skipped_by_preprocessor); + + time.ResetAndPrint("[querydb] Loading cached index file for DidOpen (blocks CodeLens)"); break; } @@ -2093,7 +2122,6 @@ bool QueryDbMainLoop( } } - response.Write(std::cerr); ipc->SendOutMessageToClient(IpcId::TextDocumentCodeAction, response); break; } diff --git a/src/indexer.cc b/src/indexer.cc index 19b0e9d1..1e23e13a 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -47,9 +47,125 @@ Range ResolveExtent(const CXCursor& cx_cursor) { return Resolve(cx_range); } + +struct NamespaceHelper { + std::unordered_map container_usr_to_qualified_name; + + void RegisterQualifiedName(std::string usr, + const CXIdxContainerInfo* container, + std::string qualified_name) { + if (container) { + std::string container_usr = clang::Cursor(container->cursor).get_usr(); + auto it = container_usr_to_qualified_name.find(container_usr); + if (it != container_usr_to_qualified_name.end()) { + container_usr_to_qualified_name[usr] = + it->second + qualified_name + "::"; + return; + } + } + + container_usr_to_qualified_name[usr] = qualified_name + "::"; + } + + std::string QualifiedName(const CXIdxContainerInfo* container, + std::string unqualified_name) { + if (container) { + std::string container_usr = clang::Cursor(container->cursor).get_usr(); + auto it = container_usr_to_qualified_name.find(container_usr); + if (it != container_usr_to_qualified_name.end()) + return it->second + unqualified_name; + + // Anonymous namespaces are not processed by indexDeclaration. If we + // encounter one insert it into map. + if (container->cursor.kind == CXCursor_Namespace) { + // assert(clang::Cursor(container->cursor).get_spelling() == ""); + container_usr_to_qualified_name[container_usr] = "::"; + return "::" + unqualified_name; + } + } + return unqualified_name; + } +}; + +struct IndexParam { + // Only use this when strictly needed (ie, primary translation unit is + // needed). Most logic should get the IndexFile instance via + // |file_consumer|. + // + // This can be null if we're not generating an index for the primary + // translation unit. + IndexFile* primary_file = nullptr; + + clang::TranslationUnit* tu = nullptr; + + FileConsumer* file_consumer = nullptr; + NamespaceHelper ns; + + IndexParam(clang::TranslationUnit* tu, FileConsumer* file_consumer) : tu(tu), file_consumer(file_consumer) {} +}; + +IndexFile* ConsumeFile(IndexParam* param, CXFile file) { + bool is_first_ownership = false; + IndexFile* db = param->file_consumer->TryConsumeFile(file, &is_first_ownership); + + // Mark dependency in primary file. If primary_file is null that means we're + // doing a re-index in which case the dependency has already been established + // in a previous index run. + if (is_first_ownership && param->primary_file) + param->primary_file->dependencies.push_back(db->path); + + if (is_first_ownership) { + // Report skipped source range list. + CXSourceRangeList* skipped = clang_getSkippedRanges(param->tu->cx_tu, file); + for (unsigned i = 0; i < skipped->count; ++i) { + Range range = Resolve(skipped->ranges[i]); + // clang_getSkippedRanges reports start one token after the '#', move it + // back so it starts at the '#' + range.start.column -= 1; + db->skipped_by_preprocessor.push_back(range); + } + clang_disposeSourceRangeList(skipped); + } + + return db; +} + } // namespace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IndexFile::IndexFile(const std::string& path) : id_cache(path), path(path) { // TODO: Reconsider if we should still be reusing the same id_cache. // Preallocate any existing resolved ids. @@ -156,60 +272,6 @@ bool Contains(const std::vector& vec, const T& element) { return false; } -struct NamespaceHelper { - std::unordered_map container_usr_to_qualified_name; - - void RegisterQualifiedName(std::string usr, - const CXIdxContainerInfo* container, - std::string qualified_name) { - if (container) { - std::string container_usr = clang::Cursor(container->cursor).get_usr(); - auto it = container_usr_to_qualified_name.find(container_usr); - if (it != container_usr_to_qualified_name.end()) { - container_usr_to_qualified_name[usr] = - it->second + qualified_name + "::"; - return; - } - } - - container_usr_to_qualified_name[usr] = qualified_name + "::"; - } - - std::string QualifiedName(const CXIdxContainerInfo* container, - std::string unqualified_name) { - if (container) { - std::string container_usr = clang::Cursor(container->cursor).get_usr(); - auto it = container_usr_to_qualified_name.find(container_usr); - if (it != container_usr_to_qualified_name.end()) - return it->second + unqualified_name; - - // Anonymous namespaces are not processed by indexDeclaration. If we - // encounter one insert it into map. - if (container->cursor.kind == CXCursor_Namespace) { - // assert(clang::Cursor(container->cursor).get_spelling() == ""); - container_usr_to_qualified_name[container_usr] = "::"; - return "::" + unqualified_name; - } - } - return unqualified_name; - } -}; - -struct IndexParam { - // Only use this when strictly needed (ie, primary translation unit is - // needed). Most logic should get the IndexFile instance via - // |file_consumer|. - // - // This can be null if we're not generating an index for the primary - // translation unit. - IndexFile* primary_file; - - FileConsumer* file_consumer; - NamespaceHelper ns; - - IndexParam(FileConsumer* file_consumer) : file_consumer(file_consumer) {} -}; - int abortQuery(CXClientData client_data, void* reserved) { // 0 -> continue return 0; @@ -232,15 +294,11 @@ void diagnostic(CXClientData client_data, CXFile file; unsigned int line, column; clang_getSpellingLocation(diag_loc, &file, &line, &column, nullptr); - bool is_first_time_visiting_file = false; - IndexFile* db = param->file_consumer->TryConsumeFile(file, &is_first_time_visiting_file); + IndexFile* db = ConsumeFile(param, file); if (!db) continue; - if (is_first_time_visiting_file && param->primary_file) - param->primary_file->dependencies.push_back(db->path); // Build diagnostic. - optional ls_diagnostic = BuildDiagnostic(diagnostic); if (ls_diagnostic) db->diagnostics.push_back(*ls_diagnostic); @@ -707,14 +765,10 @@ void indexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { CXFile file; clang_getSpellingLocation(clang_indexLoc_getCXSourceLocation(decl->loc), &file, nullptr, nullptr, nullptr); IndexParam* param = static_cast(client_data); - bool is_first_time_visiting_file = false; - IndexFile* db = param->file_consumer->TryConsumeFile(file, &is_first_time_visiting_file); + IndexFile* db = ConsumeFile(param, file); if (!db) return; - if (is_first_time_visiting_file && param->primary_file) - param->primary_file->dependencies.push_back(db->path); - NamespaceHelper* ns = ¶m->ns; @@ -1165,14 +1219,10 @@ void indexEntityReference(CXClientData client_data, CXFile file; clang_getSpellingLocation(clang_indexLoc_getCXSourceLocation(ref->loc), &file, nullptr, nullptr, nullptr); IndexParam* param = static_cast(client_data); - bool is_first_time_visiting_file = false; - IndexFile* db = param->file_consumer->TryConsumeFile(file, &is_first_time_visiting_file); + IndexFile* db = ConsumeFile(param, file); if (!db) return; - if (is_first_time_visiting_file && param->primary_file) - param->primary_file->dependencies.push_back(db->path); - // ref->cursor mainFile=0 // ref->loc mainFile=1 // ref->referencedEntity mainFile=1 @@ -1369,10 +1419,10 @@ std::vector> Parse( CXUnsavedFile unsaved; unsaved.Filename = file_contents_path.c_str(); unsaved.Contents = file_contents->c_str(); - unsaved.Length = file_contents->size(); + unsaved.Length = (unsigned long)file_contents->size(); unsaved_files.push_back(unsaved); } - clang::TranslationUnit tu(index, file, args, unsaved_files, CXTranslationUnit_KeepGoing); + clang::TranslationUnit tu(index, file, args, unsaved_files, CXTranslationUnit_KeepGoing | CXTranslationUnit_DetailedPreprocessingRecord); perf->index_parse = timer.ElapsedMicrosecondsAndReset(); @@ -1387,11 +1437,10 @@ std::vector> Parse( }; FileConsumer file_consumer(file_consumer_shared); - IndexParam param(&file_consumer); + IndexParam param(&tu, &file_consumer); CXFile cx_file = clang_getFile(tu.cx_tu, file.c_str()); - bool is_first_ownership; - param.primary_file = file_consumer.TryConsumeFile(cx_file, &is_first_ownership); + param.primary_file = ConsumeFile(¶m, cx_file); //std::cerr << "!! [START] Indexing " << file << std::endl; CXIndexAction index_action = clang_IndexAction_create(index.cx_index); @@ -1405,13 +1454,6 @@ std::vector> Parse( auto result = param.file_consumer->TakeLocalState(); for (auto& entry : result) { - // TODO: only store the path on one of these. - // TODO: These NormalizePath call should be not needed. - assert(entry->path == NormalizePath(entry->path)); - assert(entry->id_cache.primary_file == entry->path); - entry->path = NormalizePath(entry->path); - entry->id_cache.primary_file = entry->path; - entry->last_modification_time = GetLastModificationTime(entry->path); entry->import_file = file; entry->args = args; diff --git a/src/indexer.h b/src/indexer.h index a2fd39e5..6dbe5dc8 100644 --- a/src/indexer.h +++ b/src/indexer.h @@ -509,6 +509,9 @@ struct IndexFile { // Diagnostics found when indexing the file. This is not saved. NonElidedVector diagnostics; + // Source ranges that were not processed. + std::vector skipped_by_preprocessor; + std::vector dependencies; std::vector types; std::vector funcs; diff --git a/src/ipc.cc b/src/ipc.cc index 51f070d2..4146b96c 100644 --- a/src/ipc.cc +++ b/src/ipc.cc @@ -47,6 +47,9 @@ const char* IpcIdToString(IpcId id) { case IpcId::WorkspaceSymbol: return "workspace/symbol"; + case IpcId::CqueryPublishInactiveRegions: + return "$cquery/publishInactiveRegions"; + case IpcId::CqueryFreshenIndex: return "$cquery/freshenIndex"; diff --git a/src/ipc.h b/src/ipc.h index cd2a84e6..9db4a090 100644 --- a/src/ipc.h +++ b/src/ipc.h @@ -29,6 +29,9 @@ enum class IpcId : int { CodeLensResolve, WorkspaceSymbol, + // Custom notifications + CqueryPublishInactiveRegions, + // Custom messages CqueryFreshenIndex, diff --git a/src/language_server_api.h b/src/language_server_api.h index abae1dc5..34c6d519 100644 --- a/src/language_server_api.h +++ b/src/language_server_api.h @@ -1575,6 +1575,18 @@ void Reflect(TVisitor& visitor, Out_ShowLogMessage& value) { } +struct Out_CquerySetInactiveRegion : public lsOutMessage { + struct Params { + lsDocumentUri uri; + NonElidedVector inactiveRegions; + }; + std::string method = "$cquery/setInactiveRegions"; + Params params; +}; +MAKE_REFLECT_STRUCT(Out_CquerySetInactiveRegion::Params, uri, inactiveRegions); +MAKE_REFLECT_STRUCT(Out_CquerySetInactiveRegion, jsonrpc, method, params); + + struct Ipc_CqueryFreshenIndex : public IpcMessage { const static IpcId kIpcId = IpcId::CqueryFreshenIndex; lsRequestId id; diff --git a/src/serializer.cc b/src/serializer.cc index 11738448..59085700 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -199,6 +199,7 @@ void Reflect(TVisitor& visitor, IndexFile& value) { REFLECT_MEMBER(args); } REFLECT_MEMBER(dependencies); + REFLECT_MEMBER(skipped_by_preprocessor); REFLECT_MEMBER(types); REFLECT_MEMBER(funcs); REFLECT_MEMBER(vars); diff --git a/tests/preprocessor/skipped.cc b/tests/preprocessor/skipped.cc new file mode 100644 index 00000000..3f618946 --- /dev/null +++ b/tests/preprocessor/skipped.cc @@ -0,0 +1,21 @@ + +#ifdef FOOBAR +void hello(); +#endif + +#if false + + + +#endif + +#if defined(OS_FOO) + +#endif + +/* +OUTPUT: +{ + "skipped_by_preprocessor": ["2:1-4:7", "6:1-10:7", "12:1-14:7"] +} +*/ diff --git a/tests/templates/template_class_type_usage_folded_into_one.cc b/tests/templates/template_class_type_usage_folded_into_one.cc index 2fcaca81..289b330b 100644 --- a/tests/templates/template_class_type_usage_folded_into_one.cc +++ b/tests/templates/template_class_type_usage_folded_into_one.cc @@ -30,6 +30,7 @@ VarDecl b /* OUTPUT: { + "skipped_by_preprocessor": ["12:1-28:7"], "types": [{ "id": 0, "usr": "c:@E@A", diff --git a/tests/templates/template_var_usage_folded_into_one.cc b/tests/templates/template_var_usage_folded_into_one.cc index a55aa064..2bbe34bd 100644 --- a/tests/templates/template_var_usage_folded_into_one.cc +++ b/tests/templates/template_var_usage_folded_into_one.cc @@ -30,6 +30,7 @@ UnexposedDecl var /* OUTPUT: { + "skipped_by_preprocessor": ["12:1-28:7"], "types": [{ "id": 0, "usr": "c:@E@A", diff --git a/tests/usage/type_usage_as_template_parameter_complex.cc b/tests/usage/type_usage_as_template_parameter_complex.cc index c8c2d6ca..022d843a 100644 --- a/tests/usage/type_usage_as_template_parameter_complex.cc +++ b/tests/usage/type_usage_as_template_parameter_complex.cc @@ -81,6 +81,7 @@ unique_ptr* Foo::foo() { return nullptr; } /* OUTPUT: { + "skipped_by_preprocessor": ["7:1-14:7", "17:1-32:7", "35:1-39:7", "42:1-52:7", "57:1-63:7", "68:1-78:7"], "types": [{ "id": 0, "usr": "c:@ST>2#T#T@unique_ptr",