diff --git a/src/clang_complete.cc b/src/clang_complete.cc index ff957a8c..fbb3b136 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -383,7 +383,7 @@ void TryEnsureDocumentParsed(ClangCompleteManager* manager, unsaved, Flags()); // Build diagnostics. - if (manager->config_->diagnosticsOnParse && *tu) { + if (manager->config_->diagnostics.onParse && *tu) { // If we're emitting diagnostics, do an immediate reparse, otherwise we will // emit stale/bad diagnostics. *tu = ClangTranslationUnit::Reparse(std::move(*tu), unsaved); diff --git a/src/command_line.cc b/src/command_line.cc index 1611cb0d..803967f4 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -2,6 +2,7 @@ #include "cache_manager.h" #include "clang_complete.h" #include "code_complete_cache.h" +#include "diagnostics_engine.h" #include "file_consumer.h" #include "import_manager.h" #include "import_pipeline.h" @@ -177,11 +178,12 @@ void RunQueryDbThread(const std::string& bin_name, SemanticHighlightSymbolCache semantic_cache; WorkingFiles working_files; FileConsumerSharedState file_consumer_shared; + DiagnosticsEngine diag_engine(config); ClangCompleteManager clang_complete( config, &project, &working_files, [&](std::string path, std::vector diagnostics) { - EmitDiagnostics(&working_files, path, diagnostics); + diag_engine.Publish(&working_files, path, diagnostics); }, [&](ClangTranslationUnit* tu, const std::vector& unsaved, const std::string& path, const std::vector& args) { @@ -214,6 +216,7 @@ void RunQueryDbThread(const std::string& bin_name, handler->db = &db; handler->waiter = indexer_waiter; handler->project = &project; + handler->diag_engine = &diag_engine; handler->file_consumer_shared = &file_consumer_shared; handler->import_manager = &import_manager; handler->import_pipeline_status = &import_pipeline_status; diff --git a/src/config.h b/src/config.h index 5401b1cd..b9b30b65 100644 --- a/src/config.h +++ b/src/config.h @@ -72,9 +72,6 @@ struct Config { // If true, document links are reported for #include directives. bool showDocumentLinksOnIncludes = true; - // If true, diagnostics from a full document parse will be reported. - bool diagnosticsOnParse = true; - // Version of the client. If undefined the version check is skipped. Used to // inform users their vscode client is too old and needs to be updated. optional clientVersion; @@ -144,6 +141,23 @@ struct Config { std::vector includeWhitelist; } completion; + struct Diagnostics { + // Like index.{whitelist,blacklist}, don't publish diagnostics to + // blacklisted files. + std::vector blacklist; + + // How often should cquery publish diagnostics in completion? + // -1: never + // 0: as often as possible + // xxx: at most every xxx milliseconds + int frequencyMs = 0; + + // If true, diagnostics from a full document parse will be reported. + bool onParse = true; + + std::vector whitelist; + } diagnostics; + struct Index { // Attempt to convert calls of make* functions to constructors based on // hueristics. @@ -210,6 +224,11 @@ MAKE_REFLECT_STRUCT(Config::Completion, includeMaxPathSize, includeSuffixWhitelist, includeWhitelist); +MAKE_REFLECT_STRUCT(Config::Diagnostics, + blacklist, + frequencyMs, + onParse, + whitelist) MAKE_REFLECT_STRUCT(Config::Index, attributeMakeCallsToCtor, blacklist, @@ -233,13 +252,12 @@ MAKE_REFLECT_STRUCT(Config, showDocumentLinksOnIncludes, - diagnosticsOnParse, - clientVersion, client, codeLens, completion, + diagnostics, index, workspaceSymbol, xref, diff --git a/src/diagnostics_engine.cc b/src/diagnostics_engine.cc new file mode 100644 index 00000000..cd77eb96 --- /dev/null +++ b/src/diagnostics_engine.cc @@ -0,0 +1,30 @@ +#include "diagnostics_engine.h" + +#include "queue_manager.h" + +#include + +DiagnosticsEngine::DiagnosticsEngine(Config* config) + : match_(config->diagnostics.whitelist, config->diagnostics.blacklist) {} + +void DiagnosticsEngine::Publish(WorkingFiles* working_files, + std::string path, + std::vector diagnostics) { + // Cache diagnostics so we can show fixits. + working_files->DoActionOnFile(path, [&](WorkingFile* working_file) { + if (working_file) + working_file->diagnostics_ = diagnostics; + }); + + int64_t now = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count(); + if (frequencyMs_ >= 0 && (nextPublish_ <= now || diagnostics.empty()) && + match_.IsMatch(path)) { + nextPublish_ = now + frequencyMs_; + + Out_TextDocumentPublishDiagnostics out; + out.params.uri = lsDocumentUri::FromPath(path); + out.params.diagnostics = diagnostics; + QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); + } +} diff --git a/src/diagnostics_engine.h b/src/diagnostics_engine.h new file mode 100644 index 00000000..40562e26 --- /dev/null +++ b/src/diagnostics_engine.h @@ -0,0 +1,17 @@ +#include "lsp_diagnostic.h" + +#include "match.h" +#include "working_files.h" + +class DiagnosticsEngine { + int frequencyMs_ = 0; + GroupMatch match_; + int64_t nextPublish_ = 0; + + public: + DiagnosticsEngine(Config* config); + void SetFrequencyMs(int ms) { frequencyMs_ = ms; } + void Publish(WorkingFiles* working_files, + std::string path, + std::vector diagnostics); +}; diff --git a/src/import_pipeline.cc b/src/import_pipeline.cc index 2a133032..0c3f1a7e 100644 --- a/src/import_pipeline.cc +++ b/src/import_pipeline.cc @@ -2,6 +2,7 @@ #include "cache_manager.h" #include "config.h" +#include "diagnostics_engine.h" #include "iindexer.h" #include "import_manager.h" #include "lsp.h" @@ -340,6 +341,7 @@ std::vector PreloadFileContents( } void ParseFile(Config* config, + DiagnosticsEngine* diag_engine, WorkingFiles* working_files, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, @@ -394,7 +396,8 @@ void ParseFile(Config* config, // to identify indexing problems. For interactive sessions, diagnostics are // handled by code completion. if (!request.is_interactive) - EmitDiagnostics(working_files, new_index->path, new_index->diagnostics_); + diag_engine->Publish(working_files, new_index->path, + new_index->diagnostics_); // When main thread does IdMap request it will request the previous index if // needed. @@ -410,6 +413,7 @@ void ParseFile(Config* config, bool IndexMain_DoParse( Config* config, + DiagnosticsEngine* diag_engine, WorkingFiles* working_files, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, @@ -424,9 +428,9 @@ bool IndexMain_DoParse( Project::Entry entry; entry.filename = request->path; entry.args = request->args; - ParseFile(config, working_files, file_consumer_shared, timestamp_manager, - modification_timestamp_fetcher, import_manager, indexer, - request.value(), entry); + ParseFile(config, diag_engine, working_files, file_consumer_shared, + timestamp_manager, modification_timestamp_fetcher, import_manager, + indexer, request.value(), entry); return true; } @@ -582,6 +586,7 @@ void IndexWithTuFromCodeCompletion( } void Indexer_Main(Config* config, + DiagnosticsEngine* diag_engine, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, ImportManager* import_manager, @@ -609,11 +614,11 @@ void Indexer_Main(Config* config, // IndexMain_DoCreateIndexUpdate so we don't starve querydb from doing any // work. Running both also lets the user query the partially constructed // index. - did_work = - IndexMain_DoParse(config, working_files, file_consumer_shared, - timestamp_manager, &modification_timestamp_fetcher, - import_manager, indexer.get()) || - did_work; + did_work = IndexMain_DoParse(config, diag_engine, working_files, + file_consumer_shared, timestamp_manager, + &modification_timestamp_fetcher, + import_manager, indexer.get()) || + did_work; did_work = IndexMain_DoCreateIndexUpdate(timestamp_manager) || did_work; @@ -767,9 +772,10 @@ TEST_SUITE("ImportPipeline") { } bool PumpOnce() { - return IndexMain_DoParse( - &config, &working_files, &file_consumer_shared, ×tamp_manager, - &modification_timestamp_fetcher, &import_manager, indexer.get()); + return IndexMain_DoParse(&config, &diag_engine, &working_files, + &file_consumer_shared, ×tamp_manager, + &modification_timestamp_fetcher, &import_manager, + indexer.get()); } void MakeRequest(const std::string& path, @@ -786,6 +792,7 @@ TEST_SUITE("ImportPipeline") { QueueManager* queue = nullptr; Config config; + DiagnosticsEngine diag_engine{&config}; WorkingFiles working_files; FileConsumerSharedState file_consumer_shared; TimestampManager timestamp_manager; diff --git a/src/import_pipeline.h b/src/import_pipeline.h index 81355a29..7ede6ae6 100644 --- a/src/import_pipeline.h +++ b/src/import_pipeline.h @@ -10,6 +10,7 @@ struct ClangTranslationUnit; struct Config; +class DiagnosticsEngine; struct FileConsumerSharedState; struct ImportManager; struct MultiQueueWaiter; @@ -35,6 +36,7 @@ void IndexWithTuFromCodeCompletion( const std::vector& args); void Indexer_Main(Config* config, + DiagnosticsEngine* diag_engine, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, ImportManager* import_manager, diff --git a/src/lsp_diagnostic.cc b/src/lsp_diagnostic.cc index 6461a3ad..e6a000d7 100644 --- a/src/lsp_diagnostic.cc +++ b/src/lsp_diagnostic.cc @@ -1,20 +1,5 @@ #include "lsp_diagnostic.h" +#include "match.h" #include "queue_manager.h" #include "working_files.h" - -void EmitDiagnostics(WorkingFiles* working_files, - std::string path, - std::vector diagnostics) { - // Emit diagnostics. - Out_TextDocumentPublishDiagnostics out; - out.params.uri = lsDocumentUri::FromPath(path); - out.params.diagnostics = diagnostics; - QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); - - // Cache diagnostics so we can show fixits. - working_files->DoActionOnFile(path, [&](WorkingFile* working_file) { - if (working_file) - working_file->diagnostics_ = diagnostics; - }); -} diff --git a/src/lsp_diagnostic.h b/src/lsp_diagnostic.h index a2bd0f40..000980cd 100644 --- a/src/lsp_diagnostic.h +++ b/src/lsp_diagnostic.h @@ -99,8 +99,3 @@ void Reflect(TVisitor& visitor, Out_TextDocumentPublishDiagnostics& value) { MAKE_REFLECT_STRUCT(Out_TextDocumentPublishDiagnostics::Params, uri, diagnostics); - -struct WorkingFiles; -void EmitDiagnostics(WorkingFiles* working_files, - std::string path, - std::vector diagnostics); diff --git a/src/message_handler.h b/src/message_handler.h index c81c5835..1339e695 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -12,6 +12,7 @@ struct ClangCompleteManager; struct CodeCompleteCache; struct Config; +class DiagnosticsEngine; struct FileConsumerSharedState; struct ImportManager; struct ImportPipelineStatus; @@ -72,6 +73,7 @@ struct MessageHandler { QueryDatabase* db = nullptr; MultiQueueWaiter* waiter = nullptr; Project* project = nullptr; + DiagnosticsEngine* diag_engine = nullptr; FileConsumerSharedState* file_consumer_shared = nullptr; ImportManager* import_manager = nullptr; ImportPipelineStatus* import_pipeline_status = nullptr; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 7e8869d3..9ff5b740 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -1,4 +1,5 @@ #include "cache_manager.h" +#include "diagnostics_engine.h" #include "import_pipeline.h" #include "include_complete.h" #include "message_handler.h" @@ -581,6 +582,7 @@ struct InitializeHandler : BaseMessageHandler { EscapeFileName(config->projectRoot)); Timer time; + diag_engine->SetFrequencyMs(config->diagnostics.frequencyMs); // Open up / load the project. project->Load(config, config->extraClangArguments, @@ -604,9 +606,9 @@ struct InitializeHandler : BaseMessageHandler { LOG_S(INFO) << "Starting " << config->index.threads << " indexers"; for (int i = 0; i < config->index.threads; ++i) { WorkThread::StartThread("indexer" + std::to_string(i), [=]() { - Indexer_Main(config, file_consumer_shared, timestamp_manager, - import_manager, import_pipeline_status, project, - working_files, waiter); + Indexer_Main(config, diag_engine, file_consumer_shared, + timestamp_manager, import_manager, + import_pipeline_status, project, working_files, waiter); }); }