diff --git a/src/lsp.hh b/src/lsp.hh index 9cec2834..3c9bf1c2 100644 --- a/src/lsp.hh +++ b/src/lsp.hh @@ -219,6 +219,17 @@ struct TextDocumentDidChangeParam { std::vector contentChanges; }; +struct WorkDoneProgress { + const char *kind; + std::optional title; + std::optional message; + std::optional percentage; +}; +struct WorkDoneProgressParam { + const char *token; + WorkDoneProgress value; +}; + struct WorkspaceFolder { DocumentUri uri; std::string name; diff --git a/src/message_handler.hh b/src/message_handler.hh index 038b5bdd..7718e346 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -191,6 +191,8 @@ REFLECT_UNDERLYING_B(SymbolKind); REFLECT_STRUCT(TextDocumentIdentifier, uri); REFLECT_STRUCT(TextDocumentItem, uri, languageId, version, text); REFLECT_STRUCT(TextEdit, range, newText); +REFLECT_STRUCT(WorkDoneProgress, kind, title, message, percentage); +REFLECT_STRUCT(WorkDoneProgressParam, token, value); REFLECT_STRUCT(DiagnosticRelatedInformation, location, message); REFLECT_STRUCT(Diagnostic, range, severity, code, source, message, relatedInformation); diff --git a/src/messages/ccls_info.cc b/src/messages/ccls_info.cc index 049e1883..5d88a5d6 100644 --- a/src/messages/ccls_info.cc +++ b/src/messages/ccls_info.cc @@ -17,14 +17,14 @@ struct Out_cclsInfo { int files, funcs, types, vars; } db; struct Pipeline { - int pendingIndexRequests; + int64_t lastIdle, completed, enqueued; } pipeline; struct Project { int entries; } project; }; REFLECT_STRUCT(Out_cclsInfo::DB, files, funcs, types, vars); -REFLECT_STRUCT(Out_cclsInfo::Pipeline, pendingIndexRequests); +REFLECT_STRUCT(Out_cclsInfo::Pipeline, lastIdle, completed, enqueued); REFLECT_STRUCT(Out_cclsInfo::Project, entries); REFLECT_STRUCT(Out_cclsInfo, db, pipeline, project); } // namespace @@ -35,7 +35,9 @@ void MessageHandler::ccls_info(EmptyParam &, ReplyOnce &reply) { result.db.funcs = db->funcs.size(); result.db.types = db->types.size(); result.db.vars = db->vars.size(); - result.pipeline.pendingIndexRequests = pipeline::pending_index_requests; + result.pipeline.lastIdle = pipeline::stats.last_idle; + result.pipeline.completed = pipeline::stats.completed; + result.pipeline.enqueued = pipeline::stats.enqueued; result.project.entries = 0; for (auto &[_, folder] : project->root2folder) result.project.entries += folder.entries.size(); diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc index 90db54be..c6f1035b 100644 --- a/src/messages/textDocument_did.cc +++ b/src/messages/textDocument_did.cc @@ -44,7 +44,7 @@ void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { // pending index request. auto [lang, header] = lookupExtension(path); if ((lang != LanguageId::Unknown && !header) || - !pipeline::pending_index_requests) + pipeline::stats.completed == pipeline::stats.enqueued) pipeline::index(path, {}, IndexMode::Normal, false); if (header) project->indexRelated(path); diff --git a/src/pipeline.cc b/src/pipeline.cc index 2dc106da..6f517b3f 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,12 @@ struct PublishDiagnosticParam { std::vector diagnostics; }; REFLECT_STRUCT(PublishDiagnosticParam, uri, diagnostics); + +constexpr char index_progress_token[] = "index"; +struct WorkDoneProgressCreateParam { + const char *token = index_progress_token; +}; +REFLECT_STRUCT(WorkDoneProgressCreateParam, token); } // namespace void VFS::clear() { @@ -67,7 +74,8 @@ void standaloneInitialize(MessageHandler &, const std::string &root); namespace pipeline { std::atomic g_quit; -std::atomic loaded_ts{0}, pending_index_requests{0}, request_id{0}; +std::atomic loaded_ts{0}, request_id{0}; +IndexStats stats; int64_t tick = 0; namespace { @@ -195,9 +203,6 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, return false; auto &request = *opt_request; bool loud = request.mode != IndexMode::OnChange; - struct RAII { - ~RAII() { pending_index_requests--; } - } raii; // Dummy one to trigger refresh semantic highlight. if (request.path.empty()) { @@ -207,6 +212,9 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, return false; } + struct RAII { + ~RAII() { stats.completed++; } + } raii; if (!matcher.matches(request.path)) { LOG_IF_S(INFO, loud) << "skip " << request.path; return false; @@ -643,7 +651,9 @@ void mainLoop() { handler.manager = &manager; handler.include_complete = &include_complete; + bool work_done_created = false, in_progress = false; bool has_indexed = false; + int64_t last_completed = 0; std::deque backlog; StringMap> path2backlog; while (true) { @@ -693,6 +703,45 @@ void mainLoop() { } } + int64_t completed = stats.completed.load(std::memory_order_relaxed); + if (completed != last_completed) { + if (!work_done_created) { + WorkDoneProgressCreateParam param; + request("window/workDoneProgress/create", param); + work_done_created = true; + } + + int64_t enqueued = stats.enqueued.load(std::memory_order_relaxed); + if (completed != enqueued) { + if (!in_progress) { + WorkDoneProgressParam param; + param.token = index_progress_token; + param.value.kind = "begin"; + param.value.title = "indexing"; + notify("$/progress", param); + in_progress = true; + } + int64_t last_idle = stats.last_idle.load(std::memory_order_relaxed); + WorkDoneProgressParam param; + param.token = index_progress_token; + param.value.kind = "report"; + param.value.message = + (Twine(completed - last_idle) + "/" + Twine(enqueued - last_idle)) + .str(); + param.value.percentage = + 100.0 * (completed - last_idle) / (enqueued - last_idle); + notify("$/progress", param); + } else if (in_progress) { + stats.last_idle.store(enqueued, std::memory_order_relaxed); + WorkDoneProgressParam param; + param.token = index_progress_token; + param.value.kind = "end"; + notify("$/progress", param); + in_progress = false; + } + last_completed = completed; + } + if (did_work) { has_indexed |= indexed; if (g_quit.load(std::memory_order_relaxed)) @@ -736,16 +785,16 @@ void standalone(const std::string &root) { int entries = 0; for (auto &[_, folder] : project.root2folder) entries += folder.entries.size(); - printf("entries: %5d\n", entries); + printf("entries: %4d\n", entries); } while (1) { (void)on_indexed->dequeueAll(); - int pending = pending_index_requests; + int64_t enqueued = stats.enqueued, completed = stats.completed; if (tty) { - printf("\rpending: %5d", pending); + printf("\rcompleted: %4" PRId64 "/%" PRId64, completed, enqueued); fflush(stdout); } - if (!pending) + if (completed == enqueued) break; std::this_thread::sleep_for(std::chrono::milliseconds(100)); } @@ -756,7 +805,8 @@ void standalone(const std::string &root) { void index(const std::string &path, const std::vector &args, IndexMode mode, bool must_exist, RequestId id) { - pending_index_requests++; + if (!path.empty()) + stats.enqueued++; index_request->pushBack({path, args, mode, must_exist, std::move(id)}, mode != IndexMode::Background); } diff --git a/src/pipeline.hh b/src/pipeline.hh index 1c223307..2fd09d3f 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -39,9 +39,14 @@ enum class IndexMode { Normal, }; +struct IndexStats { + std::atomic last_idle, completed, enqueued; +}; + namespace pipeline { extern std::atomic g_quit; -extern std::atomic loaded_ts, pending_index_requests; +extern std::atomic loaded_ts; +extern IndexStats stats; extern int64_t tick; void threadEnter();