From ab48663ca0e7b82989fb18775d6d92cb9c6df639 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Fri, 30 Nov 2018 22:44:52 -0800 Subject: [PATCH] Refactor WorkingFiles and CompletionManager * WorkingFiles::files : vector -> unordered_map * Add timestamp to WorkingFile * Rename "comp-preload" thread to "preamble" * Rename CompletionManager to SemaManager as it is used by "diag" "comp" "preamble" * Rename clang_complete.* to sema_manager.* * Merge SemaManager::{preloads,sessions} * Add initialization option session.maxNum * In DiagnosticMain, if an included file was modified, cancel the DiagTask and create a PreambleTask instead. The task sets `from_diag` so as to trigger immediate DiagTask after the preamble is built. --- CMakeLists.txt | 2 +- src/config.hh | 7 +- src/indexer.cc | 9 +- src/indexer.hh | 4 +- src/message_handler.hh | 4 +- src/messages/ccls_call.cc | 7 +- src/messages/ccls_inheritance.cc | 5 +- src/messages/ccls_member.cc | 7 +- src/messages/ccls_navigate.cc | 13 +- src/messages/ccls_reload.cc | 4 +- src/messages/ccls_vars.cc | 7 +- src/messages/initialize.cc | 6 +- src/messages/textDocument_code.cc | 12 +- src/messages/textDocument_completion.cc | 14 +- src/messages/textDocument_definition.cc | 10 +- src/messages/textDocument_did.cc | 24 +- src/messages/textDocument_document.cc | 19 +- src/messages/textDocument_foldingRange.cc | 2 +- src/messages/textDocument_formatting.cc | 28 +- src/messages/textDocument_hover.cc | 9 +- src/messages/textDocument_references.cc | 10 +- src/messages/textDocument_rename.cc | 11 +- src/messages/textDocument_signatureHelp.cc | 15 +- src/messages/workspace.cc | 17 +- src/pipeline.cc | 30 +- src/pipeline.hh | 4 +- src/project.cc | 3 +- src/query.cc | 3 +- src/{clang_complete.cc => sema_manager.cc} | 309 +++++++++++---------- src/{clang_complete.hh => sema_manager.hh} | 106 +++---- src/test.cc | 8 +- src/working_files.cc | 79 +++--- src/working_files.hh | 39 +-- 33 files changed, 387 insertions(+), 440 deletions(-) rename src/{clang_complete.cc => sema_manager.cc} (70%) rename src/{clang_complete.hh => sema_manager.hh} (58%) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa116816..01f49968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,7 +180,6 @@ file(GLOB SOURCES src/*.cc src/*.h src/serializers/*.cc src/serializers/*.h target_sources(ccls PRIVATE third_party/siphash.cc) target_sources(ccls PRIVATE - src/clang_complete.cc src/clang_tu.cc src/config.cc src/filesystem.cc @@ -197,6 +196,7 @@ target_sources(ccls PRIVATE src/position.cc src/project.cc src/query.cc + src/sema_manager.cc src/serializer.cc src/test.cc src/utils.cc diff --git a/src/config.hh b/src/config.hh index 26574d15..1185697f 100644 --- a/src/config.hh +++ b/src/config.hh @@ -240,6 +240,10 @@ struct Config { std::vector whitelist; } index; + struct Session { + int maxNum = 10; + } session; + struct WorkspaceSymbol { int caseSensitivity = 1; // Maximum workspace search results. @@ -273,12 +277,13 @@ MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist, initialWhitelist, multiVersion, multiVersionBlacklist, multiVersionWhitelist, onChange, threads, trackDependency, whitelist); +MAKE_REFLECT_STRUCT(Config::Session, maxNum); MAKE_REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); MAKE_REFLECT_STRUCT(Config::Xref, maxNum); MAKE_REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, cacheDirectory, cacheFormat, clang, client, codeLens, completion, diagnostics, highlight, - index, workspaceSymbol, xref); + index, session, workspaceSymbol, xref); extern Config *g_config; diff --git a/src/indexer.cc b/src/indexer.cc index b833589f..ea9f3ca4 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -15,11 +15,11 @@ limitations under the License. #include "indexer.hh" -#include "clang_complete.hh" #include "clang_tu.hh" #include "log.hh" #include "pipeline.hh" #include "platform.hh" +#include "sema_manager.hh" #include "serializer.hh" #include @@ -1232,10 +1232,11 @@ void Init() { } std::vector> -Index(CompletionManager *completion, WorkingFiles *wfiles, VFS *vfs, +Index(SemaManager *manager, WorkingFiles *wfiles, VFS *vfs, const std::string &opt_wdir, const std::string &file, const std::vector &args, - const std::vector> &remapped, bool &ok) { + const std::vector> &remapped, + bool &ok) { ok = true; auto PCH = std::make_shared(); llvm::IntrusiveRefCntPtr FS = llvm::vfs::getRealFileSystem(); @@ -1266,7 +1267,7 @@ Index(CompletionManager *completion, WorkingFiles *wfiles, VFS *vfs, bool done_remap = false; #if 0 std::shared_ptr session = - completion->TryGetSession(file, false, false); + manager->TryGetSession(file, false, false); if (session) if (auto preamble = session->GetPreamble()) { Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(buf)); diff --git a/src/indexer.hh b/src/indexer.hh index b0ea2de1..8e55f666 100644 --- a/src/indexer.hh +++ b/src/indexer.hh @@ -324,14 +324,14 @@ struct IndexFile { std::string ToString(); }; -struct CompletionManager; +struct SemaManager; struct WorkingFiles; struct VFS; namespace idx { void Init(); std::vector> -Index(CompletionManager *complete, WorkingFiles *wfiles, VFS *vfs, +Index(SemaManager *complete, WorkingFiles *wfiles, VFS *vfs, const std::string &opt_wdir, const std::string &file, const std::vector &args, const std::vector> &remapped, diff --git a/src/message_handler.hh b/src/message_handler.hh index 8eca9375..14e64eaf 100644 --- a/src/message_handler.hh +++ b/src/message_handler.hh @@ -24,7 +24,7 @@ limitations under the License. #include namespace ccls { -struct CompletionManager; +struct SemaManager; struct VFS; struct IncludeComplete; struct Project; @@ -205,7 +205,7 @@ struct ReplyOnce { }; struct MessageHandler { - CompletionManager *clang_complete = nullptr; + SemaManager *manager = nullptr; DB *db = nullptr; IncludeComplete *include_complete = nullptr; Project *project = nullptr; diff --git a/src/messages/ccls_call.cc b/src/messages/ccls_call.cc index f3a059f0..68acc942 100644 --- a/src/messages/ccls_call.cc +++ b/src/messages/ccls_call.cc @@ -201,11 +201,10 @@ void MessageHandler::ccls_call(Reader &reader, ReplyOnce &reply) { param.levels); } else { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *working_file = wfiles->GetFileByFilename(file->def->path); - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { if (sym.kind == Kind::Func) { result = BuildInitial(this, sym.usr, param.callee, param.callType, param.qualified, param.levels); diff --git a/src/messages/ccls_inheritance.cc b/src/messages/ccls_inheritance.cc index fa453379..5352a43a 100644 --- a/src/messages/ccls_inheritance.cc +++ b/src/messages/ccls_inheritance.cc @@ -149,9 +149,8 @@ void Inheritance(MessageHandler *m, Param ¶m, ReplyOnce &reply) { QueryFile *file = m->FindFile(reply, param.textDocument.uri.GetPath()); if (!file) return; - WorkingFile *wfile = m->wfiles->GetFileByFilename(file->def->path); - - for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) + WorkingFile *wf = m->wfiles->GetFile(file->def->path); + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) if (sym.kind == Kind::Func || sym.kind == Kind::Type) { result = BuildInitial(m, sym, param.derived, param.qualified, param.levels); diff --git a/src/messages/ccls_member.cc b/src/messages/ccls_member.cc index da7db980..b204584d 100644 --- a/src/messages/ccls_member.cc +++ b/src/messages/ccls_member.cc @@ -282,11 +282,10 @@ void MessageHandler::ccls_member(Reader &reader, ReplyOnce &reply) { result.reset(); } else { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - for (SymbolRef sym : - FindSymbolsAtLocation(wfile, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { switch (sym.kind) { case Kind::Func: case Kind::Type: diff --git a/src/messages/ccls_navigate.cc b/src/messages/ccls_navigate.cc index 78b6fa74..991823f6 100644 --- a/src/messages/ccls_navigate.cc +++ b/src/messages/ccls_navigate.cc @@ -43,14 +43,13 @@ void MessageHandler::ccls_navigate(Reader &reader, Param param; Reflect(reader, param); QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); Position ls_pos = param.position; - if (wfile && wfile->index_lines.size()) - if (auto line = wfile->GetIndexPosFromBufferPos(ls_pos.line, - &ls_pos.character, false)) + if (wf->index_lines.size()) + if (auto line = + wf->GetIndexPosFromBufferPos(ls_pos.line, &ls_pos.character, false)) ls_pos.line = *line; Pos pos{(int16_t)ls_pos.line, (int16_t)ls_pos.character}; @@ -97,7 +96,7 @@ void MessageHandler::ccls_navigate(Reader &reader, } std::vector result; if (res) - if (auto ls_range = GetLsRange(wfile, *res)) { + if (auto ls_range = GetLsRange(wf, *res)) { Location &ls_loc = result.emplace_back(); ls_loc.uri = param.textDocument.uri; ls_loc.range = *ls_range; diff --git a/src/messages/ccls_reload.cc b/src/messages/ccls_reload.cc index d48d23c9..0ca2d446 100644 --- a/src/messages/ccls_reload.cc +++ b/src/messages/ccls_reload.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" #include "message_handler.hh" #include "pipeline.hh" #include "project.hh" +#include "sema_manager.hh" #include "working_files.hh" #include @@ -40,7 +40,7 @@ void MessageHandler::ccls_reload(Reader &reader) { vfs->Clear(); db->clear(); project->Index(wfiles, RequestId()); - clang_complete->FlushAllSessions(); + manager->Clear(); return; } } diff --git a/src/messages/ccls_vars.cc b/src/messages/ccls_vars.cc index ec13840c..560a07e2 100644 --- a/src/messages/ccls_vars.cc +++ b/src/messages/ccls_vars.cc @@ -32,13 +32,12 @@ void MessageHandler::ccls_vars(Reader &reader, ReplyOnce &reply) { Param param; Reflect(reader, param); QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *working_file = wfiles->GetFileByFilename(file->def->path); std::vector result; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { Usr usr = sym.usr; switch (sym.kind) { default: diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index 4582e4ac..0f8f6ca8 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" +#include "sema_manager.hh" #include "filesystem.hh" #include "include_complete.hh" #include "log.hh" @@ -241,7 +241,7 @@ void *Indexer(void *arg_) { delete arg; std::string name = "indexer" + std::to_string(idx); set_thread_name(name.c_str()); - pipeline::Indexer_Main(h->clang_complete, h->vfs, h->project, h->wfiles); + pipeline::Indexer_Main(h->manager, h->vfs, h->project, h->wfiles); return nullptr; } } // namespace @@ -337,6 +337,8 @@ void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { LOG_S(INFO) << "dispatch initial index requests"; m->project->Index(m->wfiles, reply.id); + + m->manager->sessions.SetCapacity(g_config->session.maxNum); } void MessageHandler::initialize(Reader &reader, ReplyOnce &reply) { diff --git a/src/messages/textDocument_code.cc b/src/messages/textDocument_code.cc index 9f59fcc3..aa45e134 100644 --- a/src/messages/textDocument_code.cc +++ b/src/messages/textDocument_code.cc @@ -33,13 +33,12 @@ MAKE_REFLECT_STRUCT(CodeAction, title, kind, edit); } void MessageHandler::textDocument_codeAction(CodeActionParam ¶m, ReplyOnce &reply) { - WorkingFile *wf = - wfiles->GetFileByFilename(param.textDocument.uri.GetPath()); + WorkingFile *wf = wfiles->GetFile(param.textDocument.uri.GetPath()); if (!wf) return; std::vector result; std::vector diagnostics; - wfiles->DoAction([&]() { diagnostics = wf->diagnostics_; }); + wfiles->WithLock([&]() { diagnostics = wf->diagnostics; }); for (Diagnostic &diag : diagnostics) if (diag.fixits_.size() && (param.range.Intersects(diag.range) || @@ -97,9 +96,8 @@ void MessageHandler::textDocument_codeLens(TextDocumentParam ¶m, std::string path = param.textDocument.uri.GetPath(); QueryFile *file = FindFile(reply, path); - WorkingFile *wfile = - file ? wfiles->GetFileByFilename(file->def->path) : nullptr; - if (!wfile) { + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { return; } @@ -107,7 +105,7 @@ void MessageHandler::textDocument_codeLens(TextDocumentParam ¶m, bool force_display = false) { if (!num && !force_display) return; - std::optional ls_range = GetLsRange(wfile, range); + std::optional ls_range = GetLsRange(wf, range); if (!ls_range) return; CodeLens &code_lens = result.emplace_back(); diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc index 7dd4f9db..a371c753 100644 --- a/src/messages/textDocument_completion.cc +++ b/src/messages/textDocument_completion.cc @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" #include "fuzzy_match.hh" #include "include_complete.hh" #include "log.hh" #include "message_handler.hh" #include "pipeline.hh" +#include "sema_manager.hh" #include "working_files.hh" #include @@ -451,7 +451,7 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, static CompleteConsumerCache> cache; CompletionList result; std::string path = param.textDocument.uri.GetPath(); - WorkingFile *file = wfiles->GetFileByFilename(path); + WorkingFile *file = wfiles->GetFile(path); if (!file) { return; } @@ -523,7 +523,7 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, } #endif - CompletionManager::OnComplete callback = + SemaManager::OnComplete callback = [filter, path, begin_pos, end_pos, reply, buffer_line](CodeCompleteConsumer *OptConsumer) { if (!OptConsumer) @@ -548,11 +548,9 @@ void MessageHandler::textDocument_completion(CompletionParam ¶m, cache.WithLock([&]() { Consumer.ls_items = cache.result; }); callback(&Consumer); } else { - clang_complete->completion_request_.PushBack( - std::make_unique( - reply.id, param.textDocument, begin_pos, - std::make_unique(CCOpts, false), CCOpts, - callback)); + manager->comp_tasks.PushBack(std::make_unique( + reply.id, param.textDocument.uri.GetPath(), begin_pos, + std::make_unique(CCOpts, false), CCOpts, callback)); } } } // namespace ccls diff --git a/src/messages/textDocument_definition.cc b/src/messages/textDocument_definition.cc index 0c7b853e..93fbd514 100644 --- a/src/messages/textDocument_definition.cc +++ b/src/messages/textDocument_definition.cc @@ -49,15 +49,15 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam ¶m, ReplyOnce &reply) { int file_id; QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; std::vector result; Maybe on_def; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); Position &ls_pos = param.position; - for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, ls_pos, true)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, ls_pos, true)) { // Special cases which are handled: // - symbol has declaration but no definition (ie, pure virtual) // - goto declaration while in definition of recursive type @@ -108,7 +108,7 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam ¶m, // Find the best match of the identifier at point. if (!range) { Position position = param.position; - const std::string &buffer = wfile->buffer_content; + const std::string &buffer = wf->buffer_content; std::string_view query = LexIdentifierAroundPos(position, buffer); std::string_view short_query = query; { @@ -172,7 +172,7 @@ void MessageHandler::textDocument_typeDefinition( QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); if (!file) return; - WorkingFile *working_file = wfiles->GetFileByFilename(file->def->path); + WorkingFile *working_file = wfiles->GetFile(file->def->path); std::vector result; auto Add = [&](const QueryType &type) { diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc index 0a8bbff1..3677bba8 100644 --- a/src/messages/textDocument_did.cc +++ b/src/messages/textDocument_did.cc @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" #include "include_complete.hh" #include "message_handler.hh" #include "pipeline.hh" #include "project.hh" +#include "sema_manager.hh" #include "working_files.hh" namespace ccls { @@ -26,31 +26,31 @@ void MessageHandler::textDocument_didChange(TextDocumentDidChangeParam ¶m) { wfiles->OnChange(param); if (g_config->index.onChange) pipeline::Index(path, {}, IndexMode::OnChange); - clang_complete->NotifyView(path); + manager->OnView(path); if (g_config->diagnostics.onChange >= 0) - clang_complete->DiagnosticsUpdate(path, g_config->diagnostics.onChange); + manager->ScheduleDiag(path, g_config->diagnostics.onChange); } void MessageHandler::textDocument_didClose(TextDocumentParam ¶m) { std::string path = param.textDocument.uri.GetPath(); - wfiles->OnClose(param.textDocument); - clang_complete->OnClose(path); + wfiles->OnClose(path); + manager->OnClose(path); } void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { std::string path = param.textDocument.uri.GetPath(); - WorkingFile *working_file = wfiles->OnOpen(param.textDocument); + WorkingFile *wf = wfiles->OnOpen(param.textDocument); if (std::optional cached_file_contents = pipeline::LoadIndexedContent(path)) - working_file->SetIndexContent(*cached_file_contents); + wf->SetIndexContent(*cached_file_contents); ReplyOnce reply; QueryFile *file = FindFile(reply, path); if (file) { - EmitSkippedRanges(working_file, *file); - EmitSemanticHighlight(db, working_file, *file); + EmitSkippedRanges(wf, *file); + EmitSemanticHighlight(db, wf, *file); } - include_complete->AddFile(working_file->filename); + include_complete->AddFile(wf->filename); // Submit new index request if it is not a header file or there is no // pending index request. @@ -59,12 +59,12 @@ void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { !pipeline::pending_index_requests) pipeline::Index(path, {}, IndexMode::Normal); - clang_complete->NotifyView(path); + manager->OnView(path); } void MessageHandler::textDocument_didSave(TextDocumentParam ¶m) { const std::string &path = param.textDocument.uri.GetPath(); pipeline::Index(path, {}, IndexMode::Normal); - clang_complete->NotifySave(path); + manager->OnSave(path); } } // namespace ccls diff --git a/src/messages/textDocument_document.cc b/src/messages/textDocument_document.cc index d8a07f0f..5ad1b26b 100644 --- a/src/messages/textDocument_document.cc +++ b/src/messages/textDocument_document.cc @@ -45,14 +45,13 @@ void MessageHandler::textDocument_documentHighlight( TextDocumentPositionParam ¶m, ReplyOnce &reply) { int file_id; QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); std::vector result; - std::vector syms = - FindSymbolsAtLocation(wfile, file, param.position, true); + FindSymbolsAtLocation(wf, file, param.position, true); for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0) continue; @@ -90,7 +89,7 @@ MAKE_REFLECT_STRUCT(DocumentLink, range, target); void MessageHandler::textDocument_documentLink(TextDocumentParam ¶m, ReplyOnce &reply) { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - WorkingFile *wf = file ? wfiles->GetFileByFilename(file->def->path) : nullptr; + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; if (!wf) { reply.Error(ErrorCode::InternalError, "not opened"); return; @@ -165,10 +164,8 @@ void MessageHandler::textDocument_documentSymbol(Reader &reader, int file_id; QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id); - if (!file) - return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - if (!wfile) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; if (param.startLine >= 0) { @@ -193,14 +190,14 @@ void MessageHandler::textDocument_documentSymbol(Reader &reader, continue; auto &ds = r.first->second; ds = std::make_unique(); - if (auto range = GetLsRange(wfile, sym.range)) { + if (auto range = GetLsRange(wf, sym.range)) { ds->selectionRange = *range; ds->range = ds->selectionRange; // For a macro expansion, M(name), we may use `M` for extent and `name` // for spell, do the check as selectionRange must be a subrange of // range. if (sym.extent.Valid()) - if (auto range1 = GetLsRange(wfile, sym.extent); + if (auto range1 = GetLsRange(wf, sym.extent); range1 && range1->Includes(*range)) ds->range = *range1; } diff --git a/src/messages/textDocument_foldingRange.cc b/src/messages/textDocument_foldingRange.cc index 62a62fe3..bf1c1ffc 100644 --- a/src/messages/textDocument_foldingRange.cc +++ b/src/messages/textDocument_foldingRange.cc @@ -34,7 +34,7 @@ void MessageHandler::textDocument_foldingRange(TextDocumentParam ¶m, QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); if (!file) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); + WorkingFile *wfile = wfiles->GetFile(file->def->path); if (!wfile) return; std::vector result; diff --git a/src/messages/textDocument_formatting.cc b/src/messages/textDocument_formatting.cc index 271d1a1d..4d86898e 100644 --- a/src/messages/textDocument_formatting.cc +++ b/src/messages/textDocument_formatting.cc @@ -81,41 +81,35 @@ void Format(ReplyOnce &reply, WorkingFile *wfile, tooling::Range range) { void MessageHandler::textDocument_formatting(DocumentFormattingParam ¶m, ReplyOnce &reply) { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - if (!wfile) - return; - Format(reply, wfile, {0, (unsigned)wfile->buffer_content.size()}); + Format(reply, wf, {0, (unsigned)wf->buffer_content.size()}); } void MessageHandler::textDocument_onTypeFormatting( DocumentOnTypeFormattingParam ¶m, ReplyOnce &reply) { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - if (!wfile) - return; - std::string_view code = wfile->buffer_content; + std::string_view code = wf->buffer_content; int pos = GetOffsetForPosition(param.position, code); auto lbrace = code.find_last_of('{', pos); if (lbrace == std::string::npos) lbrace = pos; - Format(reply, wfile, {(unsigned)lbrace, unsigned(pos - lbrace)}); + Format(reply, wf, {(unsigned)lbrace, unsigned(pos - lbrace)}); } void MessageHandler::textDocument_rangeFormatting( DocumentRangeFormattingParam ¶m, ReplyOnce &reply) { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - if (!wfile) - return; - std::string_view code = wfile->buffer_content; + std::string_view code = wf->buffer_content; int begin = GetOffsetForPosition(param.range.start, code), end = GetOffsetForPosition(param.range.end, code); - Format(reply, wfile, {(unsigned)begin, unsigned(end - begin)}); + Format(reply, wf, {(unsigned)begin, unsigned(end - begin)}); } } // namespace ccls diff --git a/src/messages/textDocument_hover.cc b/src/messages/textDocument_hover.cc index 182104de..89ecd9b3 100644 --- a/src/messages/textDocument_hover.cc +++ b/src/messages/textDocument_hover.cc @@ -94,15 +94,14 @@ GetHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) { void MessageHandler::textDocument_hover(TextDocumentPositionParam ¶m, ReplyOnce &reply) { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); Hover result; - for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { std::optional ls_range = - GetLsRange(wfiles->GetFileByFilename(file->def->path), sym.range); + GetLsRange(wfiles->GetFile(file->def->path), sym.range); if (!ls_range) continue; diff --git a/src/messages/textDocument_references.cc b/src/messages/textDocument_references.cc index b3604068..db026eb0 100644 --- a/src/messages/textDocument_references.cc +++ b/src/messages/textDocument_references.cc @@ -45,10 +45,8 @@ void MessageHandler::textDocument_references(Reader &reader, ReplyOnce &reply) { ReferenceParam param; Reflect(reader, param); QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); - if (!file) - return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); - if (!wfile) + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; for (auto &folder : param.folders) EnsureEndsInSlash(folder); @@ -58,7 +56,7 @@ void MessageHandler::textDocument_references(Reader &reader, ReplyOnce &reply) { std::unordered_set seen_uses; int line = param.position.line; - for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { // Found symbol. Return references. std::unordered_set seen; seen.insert(sym.usr); @@ -107,7 +105,7 @@ void MessageHandler::textDocument_references(Reader &reader, ReplyOnce &reply) { // = 0, // use the current filename. std::string path; - if (line == 0 || line >= (int)wfile->buffer_lines.size() - 1) + if (line == 0 || line >= (int)wf->buffer_lines.size() - 1) path = file->def->path; for (const IndexInclude &include : file->def->includes) if (include.line == param.position.line) { diff --git a/src/messages/textDocument_rename.cc b/src/messages/textDocument_rename.cc index 2274e872..e6ea5b96 100644 --- a/src/messages/textDocument_rename.cc +++ b/src/messages/textDocument_rename.cc @@ -38,7 +38,7 @@ WorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, const std::string &path = file.def->path; path_to_edit[file_id].textDocument.uri = DocumentUri::FromPath(path); - WorkingFile *working_file = wfiles->GetFileByFilename(path); + WorkingFile *working_file = wfiles->GetFile(path); if (working_file) path_to_edit[file_id].textDocument.version = working_file->version; } @@ -56,14 +56,13 @@ WorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, } // namespace void MessageHandler::textDocument_rename(RenameParam ¶m, ReplyOnce &reply) { - int file_id; - QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id); - if (!file) + QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) return; - WorkingFile *wfile = wfiles->GetFileByFilename(file->def->path); WorkspaceEdit result; - for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) { + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { result = BuildWorkspaceEdit(db, wfiles, sym, param.newName); break; } diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc index b3fa946f..21238898 100644 --- a/src/messages/textDocument_signatureHelp.cc +++ b/src/messages/textDocument_signatureHelp.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" #include "message_handler.hh" #include "pipeline.hh" +#include "sema_manager.hh" #include @@ -156,14 +156,14 @@ void MessageHandler::textDocument_signatureHelp( std::string path = param.textDocument.uri.GetPath(); Position begin_pos = param.position; - if (WorkingFile *file = wfiles->GetFileByFilename(path)) { + if (WorkingFile *file = wfiles->GetFile(path)) { std::string completion_text; Position end_pos = param.position; begin_pos = file->FindStableCompletionSource(param.position, &completion_text, &end_pos); } - CompletionManager::OnComplete callback = + SemaManager::OnComplete callback = [reply, path, begin_pos](CodeCompleteConsumer *OptConsumer) { if (!OptConsumer) return; @@ -187,11 +187,10 @@ void MessageHandler::textDocument_signatureHelp( cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; }); callback(&Consumer); } else { - clang_complete->completion_request_.PushBack( - std::make_unique( - reply.id, param.textDocument, param.position, - std::make_unique(CCOpts, false), CCOpts, - callback)); + manager->comp_tasks.PushBack(std::make_unique( + reply.id, param.textDocument.uri.GetPath(), param.position, + std::make_unique(CCOpts, false), CCOpts, + callback)); } } } // namespace ccls diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc index 05d6488b..d4b4da58 100644 --- a/src/messages/workspace.cc +++ b/src/messages/workspace.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" +#include "sema_manager.hh" #include "fuzzy_match.hh" #include "log.hh" #include "message_handler.hh" @@ -37,29 +37,28 @@ void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) { project->Load(folder); project->Index(wfiles, RequestId()); - clang_complete->FlushAllSessions(); + manager->Clear(); }; void MessageHandler::workspace_didChangeWatchedFiles( DidChangeWatchedFilesParam ¶m) { for (auto &event : param.changes) { std::string path = event.uri.GetPath(); - IndexMode mode = wfiles->GetFileByFilename(path) - ? IndexMode::Normal - : IndexMode::NonInteractive; + IndexMode mode = + wfiles->GetFile(path) ? IndexMode::Normal : IndexMode::NonInteractive; switch (event.type) { case FileChangeType::Created: case FileChangeType::Changed: { pipeline::Index(path, {}, mode); if (mode == IndexMode::Normal) - clang_complete->NotifySave(path); + manager->OnSave(path); else - clang_complete->OnClose(path); + manager->OnClose(path); break; } case FileChangeType::Deleted: pipeline::Index(path, {}, mode); - clang_complete->OnClose(path); + manager->OnClose(path); break; } } @@ -91,7 +90,7 @@ void MessageHandler::workspace_didChangeWorkspaceFolders( project->Index(wfiles, RequestId()); - clang_complete->FlushAllSessions(); + manager->Clear(); } namespace { diff --git a/src/pipeline.cc b/src/pipeline.cc index 2c7b54f8..c7f8aa7c 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -15,7 +15,6 @@ limitations under the License. #include "pipeline.hh" -#include "clang_complete.hh" #include "config.hh" #include "include_complete.hh" #include "log.hh" @@ -25,6 +24,7 @@ limitations under the License. #include "platform.hh" #include "project.hh" #include "query.hh" +#include "sema_manager.hh" #include "serializers/json.hh" #include @@ -179,7 +179,7 @@ std::mutex &GetFileMutex(const std::string &path) { return mutexes[std::hash()(path) % N_MUTEXES]; } -bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles, +bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *project, VFS *vfs, const GroupMatch &matcher) { std::optional opt_request = index_request->TryPopFront(); if (!opt_request) @@ -376,11 +376,11 @@ void Init() { for_stdout = new ThreadedQueue(stdout_waiter); } -void Indexer_Main(CompletionManager *completion, VFS *vfs, Project *project, +void Indexer_Main(SemaManager *manager, VFS *vfs, Project *project, WorkingFiles *wfiles) { GroupMatch matcher(g_config->index.whitelist, g_config->index.blacklist); while (true) - if (!Indexer_Parse(completion, wfiles, project, vfs, matcher)) + if (!Indexer_Parse(manager, wfiles, project, vfs, matcher)) indexer_waiter->Wait(index_request); } @@ -388,13 +388,13 @@ void Main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) { if (update->refresh) { LOG_S(INFO) << "loaded project. Refresh semantic highlight for all working file."; - std::lock_guard lock(wfiles->files_mutex); - for (auto &f : wfiles->files) { - std::string filename = LowerPathIfInsensitive(f->filename); - if (db->name2file_id.find(filename) == db->name2file_id.end()) + std::lock_guard lock(wfiles->mutex); + for (auto &[f, wf] : wfiles->files) { + std::string path = LowerPathIfInsensitive(f); + if (db->name2file_id.find(path) == db->name2file_id.end()) continue; - QueryFile &file = db->files[db->name2file_id[filename]]; - EmitSemanticHighlight(db, f.get(), file); + QueryFile &file = db->files[db->name2file_id[path]]; + EmitSemanticHighlight(db, wf.get(), file); } return; } @@ -407,7 +407,7 @@ void Main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) { // Update indexed content, skipped ranges, and semantic highlighting. if (update->files_def_update) { auto &def_u = *update->files_def_update; - if (WorkingFile *wfile = wfiles->GetFileByFilename(def_u.first.path)) { + if (WorkingFile *wfile = wfiles->GetFile(def_u.first.path)) { // FIXME With index.onChange: true, use buffer_content only for // request.path wfile->SetIndexContent(g_config->index.onChange ? wfile->buffer_content @@ -495,7 +495,7 @@ void MainLoop() { WorkingFiles wfiles; VFS vfs; - CompletionManager clang_complete( + SemaManager manager( &project, &wfiles, [&](std::string path, std::vector diagnostics) { PublishDiagnosticParam params; @@ -521,7 +521,7 @@ void MainLoop() { handler.project = &project; handler.vfs = &vfs; handler.wfiles = &wfiles; - handler.clang_complete = &clang_complete; + handler.manager = &manager; handler.include_complete = &include_complete; bool has_indexed = false; @@ -557,12 +557,16 @@ void Standalone(const std::string &root) { Project project; WorkingFiles wfiles; VFS vfs; + SemaManager manager( + nullptr, nullptr, [&](std::string, std::vector) {}, + [](RequestId id) {}); IncludeComplete complete(&project); MessageHandler handler; handler.project = &project; handler.wfiles = &wfiles; handler.vfs = &vfs; + handler.manager = &manager; handler.include_complete = &complete; StandaloneInitialize(handler, root); diff --git a/src/pipeline.hh b/src/pipeline.hh index 93b51d1d..c75a371f 100644 --- a/src/pipeline.hh +++ b/src/pipeline.hh @@ -10,7 +10,7 @@ #include namespace ccls { -struct CompletionManager; +struct SemaManager; struct GroupMatch; struct Project; struct WorkingFiles; @@ -41,7 +41,7 @@ extern int64_t tick; void Init(); void LaunchStdin(); void LaunchStdout(); -void Indexer_Main(CompletionManager *completion, VFS *vfs, Project *project, +void Indexer_Main(SemaManager *manager, VFS *vfs, Project *project, WorkingFiles *wfiles); void MainLoop(); void Standalone(const std::string &root); diff --git a/src/project.cc b/src/project.cc index cc23c238..c2c23a6c 100644 --- a/src/project.cc +++ b/src/project.cc @@ -485,8 +485,7 @@ void Project::Index(WorkingFiles *wfiles, RequestId id) { std::string reason; if (match.Matches(entry.filename, &reason) && match_i.Matches(entry.filename, &reason)) { - bool interactive = - wfiles->GetFileByFilename(entry.filename) != nullptr; + bool interactive = wfiles->GetFile(entry.filename) != nullptr; pipeline::Index( entry.filename, entry.args, interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); diff --git a/src/query.cc b/src/query.cc index 0f767b15..036cba60 100644 --- a/src/query.cc +++ b/src/query.cc @@ -669,8 +669,7 @@ DocumentUri GetLsDocumentUri(DB *db, int file_id) { std::optional GetLsLocation(DB *db, WorkingFiles *wfiles, Use use) { std::string path; DocumentUri uri = GetLsDocumentUri(db, use.file_id, &path); - std::optional range = - GetLsRange(wfiles->GetFileByFilename(path), use.range); + std::optional range = GetLsRange(wfiles->GetFile(path), use.range); if (!range) return std::nullopt; return Location{uri, *range}; diff --git a/src/clang_complete.cc b/src/sema_manager.cc similarity index 70% rename from src/clang_complete.cc rename to src/sema_manager.cc index 3132a523..f5dee411 100644 --- a/src/clang_complete.cc +++ b/src/sema_manager.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "clang_complete.hh" +#include "sema_manager.hh" #include "clang_tu.hh" #include "filesystem.hh" @@ -81,6 +81,8 @@ TextEdit ToTextEdit(const clang::SourceManager &SM, const clang::LangOptions &L, return edit; } +using IncludeStructure = std::vector>; + struct PreambleStatCache { llvm::StringMap> Cache; @@ -132,11 +134,13 @@ struct PreambleStatCache { }; struct PreambleData { - PreambleData(clang::PrecompiledPreamble P, std::vector diags, + PreambleData(clang::PrecompiledPreamble P, IncludeStructure includes, + std::vector diags, std::unique_ptr stat_cache) - : Preamble(std::move(P)), diags(std::move(diags)), - stat_cache(std::move(stat_cache)) {} + : Preamble(std::move(P)), includes(std::move(includes)), + diags(std::move(diags)), stat_cache(std::move(stat_cache)) {} clang::PrecompiledPreamble Preamble; + IncludeStructure includes; std::vector diags; std::unique_ptr stat_cache; }; @@ -173,7 +177,40 @@ CharSourceRange DiagnosticRange(const clang::Diagnostic &D, const LangOptions &L return R; } +class StoreInclude : public PPCallbacks { + const SourceManager &SM; + IncludeStructure &out; + DenseSet Seen; +public: + StoreInclude(const SourceManager &SM, IncludeStructure &out) + : SM(SM), out(out) {} + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const clang::Module *Imported +#if LLVM_VERSION_MAJOR >= 7 + , SrcMgr::CharacteristicKind FileKind +#endif + ) override { + (void)SM; + if (File && Seen.insert(File).second) + out.emplace_back(PathFromFileEntry(*File), File->getModificationTime()); + } +}; + +class CclsPreambleCallbacks : public PreambleCallbacks { +public: + void BeforeExecute(CompilerInstance &CI) override { + SM = &CI.getSourceManager(); + } + std::unique_ptr createPPCallbacks() override { + return std::make_unique(*SM, includes); + } + SourceManager *SM = nullptr; + IncludeStructure includes; +}; class StoreDiags : public DiagnosticConsumer { const LangOptions *LangOpts; @@ -262,7 +299,7 @@ public: }; std::unique_ptr BuildCompilerInstance( - CompletionSession &session, std::unique_ptr CI, + Session &session, std::unique_ptr CI, IntrusiveRefCntPtr FS, DiagnosticConsumer &DC, const PreambleData *preamble, const std::string &path, std::unique_ptr &Buf) { @@ -303,16 +340,17 @@ bool Parse(CompilerInstance &Clang) { return true; } -void BuildPreamble(CompletionSession &session, CompilerInvocation &CI, +void BuildPreamble(Session &session, CompilerInvocation &CI, IntrusiveRefCntPtr FS, - const std::string &main, + const SemaManager::PreambleTask &task, std::unique_ptr stat_cache) { std::shared_ptr OldP = session.GetPreamble(); - std::string content = session.wfiles->GetContent(main); + std::string content = session.wfiles->GetContent(task.path); std::unique_ptr Buf = llvm::MemoryBuffer::getMemBuffer(content); auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0); - if (OldP && OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get())) + if (!task.from_diag && OldP && + OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get())) return; // -Werror makes warnings issued as errors, which stops parsing // prematurely because of -ferror-limit=. This also works around the issue @@ -323,70 +361,88 @@ void BuildPreamble(CompletionSession &session, CompilerInvocation &CI, CI.getFrontendOpts().SkipFunctionBodies = true; CI.getLangOpts()->CommentOpts.ParseAllComments = g_config->index.comments > 1; - StoreDiags DC(main); + StoreDiags DC(task.path); IntrusiveRefCntPtr DE = CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false); - PreambleCallbacks PP; + if (OldP) { + std::lock_guard lock(session.wfiles->mutex); + for (auto &include : OldP->includes) + if (WorkingFile *wf = session.wfiles->GetFileUnlocked(include.first)) + CI.getPreprocessorOpts().addRemappedFile( + include.first, + llvm::MemoryBuffer::getMemBufferCopy(wf->buffer_content).release()); + } + + CclsPreambleCallbacks PC; if (auto NewPreamble = PrecompiledPreamble::Build( - CI, Buf.get(), Bounds, *DE, FS, session.PCH, true, PP)) { + CI, Buf.get(), Bounds, *DE, FS, session.PCH, true, PC)) { + assert(!CI.getPreprocessorOpts().RetainRemappedFileBuffers); + if (OldP) { + auto &old_includes = OldP->includes; + auto it = old_includes.begin(); + std::sort(PC.includes.begin(), PC.includes.end()); + for (auto &include : PC.includes) + if (include.second == 0) { + while (it != old_includes.end() && it->first < include.first) + ++it; + if (it == old_includes.end()) + break; + include.second = it->second; + } + } + std::lock_guard lock(session.mutex); session.preamble = std::make_shared( - std::move(*NewPreamble), DC.Take(), std::move(stat_cache)); + std::move(*NewPreamble), std::move(PC.includes), DC.Take(), + std::move(stat_cache)); } } -void *CompletionPreloadMain(void *manager_) { - auto *manager = static_cast(manager_); - set_thread_name("comp-preload"); +void *PreambleMain(void *manager_) { + auto *manager = static_cast(manager_); + set_thread_name("preamble"); while (true) { - auto request = manager->preload_requests_.Dequeue(); + SemaManager::PreambleTask task = manager->preamble_tasks.Dequeue(); - bool is_open = false; - std::shared_ptr session = - manager->TryGetSession(request.path, true, &is_open); - if (!session) - continue; + bool created = false; + std::shared_ptr session = + manager->EnsureSession(task.path, &created); - const auto &args = session->file.args; auto stat_cache = std::make_unique(); IntrusiveRefCntPtr FS = stat_cache->Producer(session->FS); if (std::unique_ptr CI = - BuildCompilerInvocation(args, FS)) - BuildPreamble(*session, *CI, FS, request.path, std::move(stat_cache)); + BuildCompilerInvocation(session->file.args, FS)) + BuildPreamble(*session, *CI, FS, task, std::move(stat_cache)); - int debounce = - is_open ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave; - if (debounce >= 0) { - TextDocumentIdentifier document; - document.uri = DocumentUri::FromPath(request.path); - manager->DiagnosticsUpdate(request.path, debounce); + if (task.from_diag) { + manager->ScheduleDiag(task.path, 0); + } else { + int debounce = + created ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave; + if (debounce >= 0) + manager->ScheduleDiag(task.path, debounce); } } return nullptr; } void *CompletionMain(void *manager_) { - auto *manager = static_cast(manager_); + auto *manager = static_cast(manager_); set_thread_name("comp"); while (true) { - // Fetching the completion request blocks until we have a request. - std::unique_ptr request = - manager->completion_request_.Dequeue(); + std::unique_ptr task = manager->comp_tasks.Dequeue(); // Drop older requests if we're not buffering. while (g_config->completion.dropOldRequests && - !manager->completion_request_.IsEmpty()) { - manager->on_dropped_(request->id); - request->Consumer.reset(); - request->on_complete(nullptr); - request = manager->completion_request_.Dequeue(); + !manager->comp_tasks.IsEmpty()) { + manager->on_dropped_(task->id); + task->Consumer.reset(); + task->on_complete(nullptr); + task = manager->comp_tasks.Dequeue(); } - std::string path = request->document.uri.GetPath(); - - std::shared_ptr session = - manager->TryGetSession(path, false); + std::shared_ptr session = manager->EnsureSession(task->path); std::shared_ptr preamble = session->GetPreamble(); IntrusiveRefCntPtr FS = preamble ? preamble->stat_cache->Consumer(session->FS) : session->FS; @@ -395,34 +451,34 @@ void *CompletionMain(void *manager_) { if (!CI) continue; auto &FOpts = CI->getFrontendOpts(); - FOpts.CodeCompleteOpts = request->CCOpts; - FOpts.CodeCompletionAt.FileName = path; - FOpts.CodeCompletionAt.Line = request->position.line + 1; - FOpts.CodeCompletionAt.Column = request->position.character + 1; + FOpts.CodeCompleteOpts = task->CCOpts; + FOpts.CodeCompletionAt.FileName = task->path; + FOpts.CodeCompletionAt.Line = task->position.line + 1; + FOpts.CodeCompletionAt.Column = task->position.character + 1; FOpts.SkipFunctionBodies = true; CI->getLangOpts()->CommentOpts.ParseAllComments = true; DiagnosticConsumer DC; - std::string content = manager->wfiles_->GetContent(path); + std::string content = manager->wfiles->GetContent(task->path); auto Buf = llvm::MemoryBuffer::getMemBuffer(content); bool in_preamble = - GetOffsetForPosition( - {request->position.line, request->position.character}, content) < + GetOffsetForPosition({task->position.line, task->position.character}, + content) < ComputePreambleBounds(*CI->getLangOpts(), Buf.get(), 0).Size; if (in_preamble) preamble.reset(); auto Clang = BuildCompilerInstance(*session, std::move(CI), FS, DC, - preamble.get(), path, Buf); + preamble.get(), task->path, Buf); if (!Clang) continue; Clang->getPreprocessorOpts().SingleFileParseMode = in_preamble; - Clang->setCodeCompletionConsumer(request->Consumer.release()); + Clang->setCodeCompletionConsumer(task->Consumer.release()); if (!Parse(*Clang)) continue; Buf.release(); - request->on_complete(&Clang->getCodeCompletionConsumer()); + task->on_complete(&Clang->getCodeCompletionConsumer()); } return nullptr; } @@ -456,25 +512,38 @@ void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) { } void *DiagnosticMain(void *manager_) { - auto *manager = static_cast(manager_); + auto *manager = static_cast(manager_); set_thread_name("diag"); while (true) { - CompletionManager::DiagnosticRequest request = - manager->diagnostic_request_.Dequeue(); - const std::string &path = request.path; - int64_t wait = request.wait_until - + SemaManager::DiagTask task = manager->diag_tasks.Dequeue(); + int64_t wait = task.wait_until - chrono::duration_cast( chrono::high_resolution_clock::now().time_since_epoch()) .count(); if (wait > 0) - std::this_thread::sleep_for(chrono::duration( - std::min(wait, request.debounce))); + std::this_thread::sleep_for( + chrono::duration(std::min(wait, task.debounce))); - std::shared_ptr session = - manager->TryGetSession(path, false); + std::shared_ptr session = manager->EnsureSession(task.path); std::shared_ptr preamble = session->GetPreamble(); IntrusiveRefCntPtr FS = preamble ? preamble->stat_cache->Consumer(session->FS) : session->FS; + if (preamble) { + bool rebuild = false; + { + std::lock_guard lock(manager->wfiles->mutex); + for (auto &include : preamble->includes) + if (WorkingFile *wf = manager->wfiles->GetFileUnlocked(include.first); + wf && include.second < wf->timestamp) { + include.second = wf->timestamp; + rebuild = true; + } + } + if (rebuild) { + manager->preamble_tasks.PushBack({task.path, true}, true); + continue; + } + } std::unique_ptr CI = BuildCompilerInvocation(session->file.args, FS); @@ -485,11 +554,11 @@ void *DiagnosticMain(void *manager_) { CI->getDiagnosticOpts().Warnings.push_back("no-unused-function"); CI->getDiagnosticOpts().IgnoreWarnings = false; CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking; - StoreDiags DC(path); - std::string content = manager->wfiles_->GetContent(path); + StoreDiags DC(task.path); + std::string content = manager->wfiles->GetContent(task.path); auto Buf = llvm::MemoryBuffer::getMemBuffer(content); auto Clang = BuildCompilerInstance(*session, std::move(CI), FS, DC, - preamble.get(), path, Buf); + preamble.get(), task.path, Buf); if (!Clang) continue; if (!Parse(*Clang)) @@ -552,36 +621,32 @@ void *DiagnosticMain(void *manager_) { } { - std::lock_guard lock(session->wfiles->files_mutex); - if (WorkingFile *wfile = session->wfiles->GetFileByFilenameNoLock(path)) - wfile->diagnostics_ = ls_diags; + std::lock_guard lock(manager->wfiles->mutex); + if (WorkingFile *wf = manager->wfiles->GetFileUnlocked(task.path)) + wf->diagnostics = ls_diags; } - manager->on_diagnostic_(path, ls_diags); + manager->on_diagnostic_(task.path, ls_diags); } return nullptr; } } // namespace -std::shared_ptr CompletionSession::GetPreamble() { +std::shared_ptr Session::GetPreamble() { std::lock_guard lock(mutex); return preamble; } -CompletionManager::CompletionManager(Project *project, WorkingFiles *wfiles, - OnDiagnostic on_diagnostic, - OnDropped on_dropped) - : project_(project), wfiles_(wfiles), on_diagnostic_(on_diagnostic), - on_dropped_(on_dropped), preloads(kMaxPreloadedSessions), - sessions(kMaxCompletionSessions), - PCH(std::make_shared()) { +SemaManager::SemaManager(Project *project, WorkingFiles *wfiles, + OnDiagnostic on_diagnostic, OnDropped on_dropped) + : project_(project), wfiles(wfiles), on_diagnostic_(on_diagnostic), + on_dropped_(on_dropped), PCH(std::make_shared()) { + SpawnThread(ccls::PreambleMain, this); SpawnThread(ccls::CompletionMain, this); - SpawnThread(ccls::CompletionPreloadMain, this); SpawnThread(ccls::DiagnosticMain, this); } -void CompletionManager::DiagnosticsUpdate(const std::string &path, - int debounce) { +void SemaManager::ScheduleDiag(const std::string &path, int debounce) { static GroupMatch match(g_config->diagnostics.whitelist, g_config->diagnostics.blacklist); if (!match.Matches(path)) @@ -601,78 +666,42 @@ void CompletionManager::DiagnosticsUpdate(const std::string &path, } } if (flag) - diagnostic_request_.PushBack({path, now + debounce, debounce}, false); + diag_tasks.PushBack({path, now + debounce, debounce}, false); } -void CompletionManager::NotifyView(const std::string &path) { - // Only reparse the file if we create a new CompletionSession. - if (EnsureCompletionOrCreatePreloadSession(path)) - preload_requests_.PushBack(PreloadRequest{path}, true); +void SemaManager::OnView(const std::string &path) { + std::lock_guard lock(mutex); + if (!sessions.Get(path)) + preamble_tasks.PushBack(PreambleTask{path}, true); } -void CompletionManager::NotifySave(const std::string &filename) { - EnsureCompletionOrCreatePreloadSession(filename); - preload_requests_.PushBack(PreloadRequest{filename}, true); +void SemaManager::OnSave(const std::string &path) { + preamble_tasks.PushBack(PreambleTask{path}, true); } -void CompletionManager::OnClose(const std::string &filename) { - std::lock_guard lock(sessions_lock_); - preloads.Take(filename); - sessions.Take(filename); +void SemaManager::OnClose(const std::string &path) { + std::lock_guard lock(mutex); + sessions.Take(path); } -bool CompletionManager::EnsureCompletionOrCreatePreloadSession( - const std::string &path) { - std::lock_guard lock(sessions_lock_); - if (preloads.Get(path) || sessions.Get(path)) - return false; - - // No CompletionSession, create new one. - auto session = std::make_shared( - project_->FindEntry(path, false), wfiles_, PCH); - if (session->file.filename != path) { - session->inferred = true; - session->file.filename = path; - } - preloads.Insert(path, session); - LOG_S(INFO) << "create preload session for " << path; - return true; -} - -std::shared_ptr -CompletionManager::TryGetSession(const std::string &path, bool preload, - bool *is_open) { - std::lock_guard lock(sessions_lock_); - std::shared_ptr session = preloads.Get(path); - - if (session) { - if (!preload) { - preloads.Take(path); - sessions.Insert(path, session); - if (is_open) - *is_open = true; - } - return session; - } - - session = sessions.Get(path); - if (!session && !preload) { - session = std::make_shared( - project_->FindEntry(path, false), wfiles_, PCH); - sessions.Insert(path, session); +std::shared_ptr +SemaManager::EnsureSession(const std::string &path, bool *created) { + std::lock_guard lock(mutex); + std::shared_ptr session = sessions.Get(path); + if (!session) { + session = std::make_shared( + project_->FindEntry(path, false), wfiles, PCH); LOG_S(INFO) << "create session for " << path; - if (is_open) - *is_open = true; + sessions.Insert(path, session); + if (created) + *created = true; } - return session; } -void CompletionManager::FlushAllSessions() { - LOG_S(INFO) << "flush all clang complete sessions"; - std::lock_guard lock(sessions_lock_); - - preloads.Clear(); +void SemaManager::Clear() { + LOG_S(INFO) << "clear all sessions"; + std::lock_guard lock(mutex); sessions.Clear(); } } // namespace ccls diff --git a/src/clang_complete.hh b/src/sema_manager.hh similarity index 58% rename from src/clang_complete.hh rename to src/sema_manager.hh index d067818c..632b2eaa 100644 --- a/src/clang_complete.hh +++ b/src/sema_manager.hh @@ -54,8 +54,6 @@ TextEdit ToTextEdit(const clang::SourceManager &SM, const clang::FixItHint &FixIt); template struct LruCache { - LruCache(int capacity) : capacity(capacity) {} - std::shared_ptr Get(const K &key) { for (auto it = items.begin(); it != items.end(); ++it) if (it->first == key) { @@ -81,14 +79,14 @@ template struct LruCache { items.emplace(items.begin(), key, std::move(value)); } void Clear() { items.clear(); } + void SetCapacity(int cap) { capacity = cap; } private: std::vector>> items; - int capacity; + int capacity = 1; }; -struct CompletionSession - : public std::enable_shared_from_this { +struct Session { std::mutex mutex; std::shared_ptr preamble; @@ -101,14 +99,14 @@ struct CompletionSession llvm::vfs::getRealFileSystem(); std::shared_ptr PCH; - CompletionSession(const Project::Entry &file, WorkingFiles *wfiles, + Session(const Project::Entry &file, WorkingFiles *wfiles, std::shared_ptr PCH) : file(file), wfiles(wfiles), PCH(PCH) {} std::shared_ptr GetPreamble(); }; -struct CompletionManager { +struct SemaManager { using OnDiagnostic = std::function diagnostics)>; // If OptConsumer is nullptr, the request has been cancelled. @@ -116,91 +114,57 @@ struct CompletionManager { std::function; using OnDropped = std::function; - struct PreloadRequest { + struct PreambleTask { std::string path; + bool from_diag = false; }; - struct CompletionRequest { - CompletionRequest(const RequestId &id, - const TextDocumentIdentifier &document, - const Position &position, - std::unique_ptr Consumer, - clang::CodeCompleteOptions CCOpts, - const OnComplete &on_complete) - : id(id), document(document), position(position), - Consumer(std::move(Consumer)), CCOpts(CCOpts), - on_complete(on_complete) {} + struct CompTask { + CompTask(const RequestId &id, const std::string &path, + const Position &position, + std::unique_ptr Consumer, + clang::CodeCompleteOptions CCOpts, const OnComplete &on_complete) + : id(id), path(path), position(position), Consumer(std::move(Consumer)), + CCOpts(CCOpts), on_complete(on_complete) {} RequestId id; - TextDocumentIdentifier document; + std::string path; Position position; std::unique_ptr Consumer; clang::CodeCompleteOptions CCOpts; OnComplete on_complete; }; - struct DiagnosticRequest { + struct DiagTask { std::string path; int64_t wait_until; int64_t debounce; }; - CompletionManager(Project *project, WorkingFiles *wfiles, + SemaManager(Project *project, WorkingFiles *wfiles, OnDiagnostic on_diagnostic, OnDropped on_dropped); - // Request a diagnostics update. - void DiagnosticsUpdate(const std::string &path, int debounce); - - // Notify the completion manager that |filename| has been viewed and we - // should begin preloading completion data. - void NotifyView(const std::string &path); - // Notify the completion manager that |filename| has been saved. This - // triggers a reparse. - void NotifySave(const std::string &path); - // Notify the completion manager that |filename| has been closed. Any existing - // completion session will be dropped. + void ScheduleDiag(const std::string &path, int debounce); + void OnView(const std::string &path); + void OnSave(const std::string &path); void OnClose(const std::string &path); - - // Ensures there is a completion or preloaded session. Returns true if a new - // session was created. - bool EnsureCompletionOrCreatePreloadSession(const std::string &path); - // Tries to find an edit session for |filename|. This will move the session - // from view to edit. - std::shared_ptr - TryGetSession(const std::string &path, bool preload, bool *is_open = nullptr); - - // Flushes all saved sessions - void FlushAllSessions(void); - - // TODO: make these configurable. - const int kMaxPreloadedSessions = 10; - const int kMaxCompletionSessions = 5; + std::shared_ptr EnsureSession(const std::string &path, + bool *created = nullptr); + void Clear(void); // Global state. Project *project_; - WorkingFiles *wfiles_; + WorkingFiles *wfiles; OnDiagnostic on_diagnostic_; OnDropped on_dropped_; - using LruSessionCache = LruCache; - - // CompletionSession instances which are preloaded, ie, files which the user - // has viewed but not requested code completion for. - LruSessionCache preloads; - // CompletionSession instances which the user has actually performed - // completion on. This is more rare so these instances tend to stay alive - // much longer than the ones in |preloaded_sessions_|. - LruSessionCache sessions; - // Mutex which protects |view_sessions_| and |edit_sessions_|. - std::mutex sessions_lock_; + std::mutex mutex; + LruCache sessions; std::mutex diag_mutex; std::unordered_map next_diag; - // Request a code completion at the given location. - ThreadedQueue> completion_request_; - ThreadedQueue diagnostic_request_; - // Parse requests. The path may already be parsed, in which case it should be - // reparsed. - ThreadedQueue preload_requests_; + ThreadedQueue> comp_tasks; + ThreadedQueue diag_tasks; + ThreadedQueue preamble_tasks; std::shared_ptr PCH; }; @@ -210,16 +174,14 @@ struct CompletionManager { // that happens. template struct CompleteConsumerCache { - // NOTE: Make sure to access these variables under |WithLock|. - std::optional path; - std::optional position; + std::mutex mutex; + std::string path; + Position position; T result; - std::mutex mutex; - - void WithLock(std::function action) { + template void WithLock(Fn &&fn) { std::lock_guard lock(mutex); - action(); + fn(); } bool IsCacheValid(const std::string path, Position position) { std::lock_guard lock(mutex); diff --git a/src/test.cc b/src/test.cc index 49183339..7faf948c 100644 --- a/src/test.cc +++ b/src/test.cc @@ -15,7 +15,7 @@ limitations under the License. #include "test.hh" -#include "clang_complete.hh" +#include "sema_manager.hh" #include "filesystem.hh" #include "indexer.hh" #include "pipeline.hh" @@ -288,9 +288,9 @@ bool RunIndexTests(const std::string &filter_path, bool enable_update) { bool update_all = false; // FIXME: show diagnostics in STL/headers when running tests. At the moment // this can be done by conRequestIdex index(1, 1); - CompletionManager completion(nullptr, nullptr, - [&](std::string, std::vector) {}, - [](RequestId id) {}); + SemaManager completion( + nullptr, nullptr, [&](std::string, std::vector) {}, + [](RequestId id) {}); GetFilesInFolder( "index_tests", true /*recursive*/, true /*add_folder_to_path*/, [&](const std::string &path) { diff --git a/src/working_files.cc b/src/working_files.cc index 48004e9c..0165ee96 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -21,9 +21,11 @@ limitations under the License. #include #include +#include #include #include #include +namespace chrono = std::chrono; using namespace clang; using namespace llvm; @@ -364,57 +366,53 @@ WorkingFile::FindStableCompletionSource(Position position, return GetPositionForOffset(buffer_content, i); } -WorkingFile *WorkingFiles::GetFileByFilename(const std::string &filename) { - std::lock_guard lock(files_mutex); - return GetFileByFilenameNoLock(filename); +WorkingFile *WorkingFiles::GetFile(const std::string &path) { + std::lock_guard lock(mutex); + return GetFileUnlocked(path); } -WorkingFile * -WorkingFiles::GetFileByFilenameNoLock(const std::string &filename) { - for (auto &file : files) { - if (file->filename == filename) - return file.get(); - } - return nullptr; +WorkingFile *WorkingFiles::GetFileUnlocked(const std::string &path) { + auto it = files.find(path); + return it != files.end() ? it->second.get() : nullptr; } -std::string WorkingFiles::GetContent(const std::string &filename) { - std::lock_guard lock(files_mutex); - for (auto &file : files) - if (file->filename == filename) - return file->buffer_content; - return ""; +std::string WorkingFiles::GetContent(const std::string &path) { + std::lock_guard lock(mutex); + auto it = files.find(path); + return it != files.end() ? it->second->buffer_content : ""; } WorkingFile *WorkingFiles::OnOpen(const TextDocumentItem &open) { - std::lock_guard lock(files_mutex); + std::lock_guard lock(mutex); - std::string filename = open.uri.GetPath(); + std::string path = open.uri.GetPath(); std::string content = open.text; - // The file may already be open. - if (WorkingFile *file = GetFileByFilenameNoLock(filename)) { - file->version = open.version; - file->buffer_content = content; - file->OnBufferContentUpdated(); - return file; + auto &wf = files[path]; + if (wf) { + wf->version = open.version; + wf->buffer_content = content; + wf->OnBufferContentUpdated(); + } else { + wf = std::make_unique(path, content); } - - files.push_back(std::make_unique(filename, content)); - return files[files.size() - 1].get(); + return wf.get(); } void WorkingFiles::OnChange(const TextDocumentDidChangeParam &change) { - std::lock_guard lock(files_mutex); + std::lock_guard lock(mutex); - std::string filename = change.textDocument.uri.GetPath(); - WorkingFile *file = GetFileByFilenameNoLock(filename); + std::string path = change.textDocument.uri.GetPath(); + WorkingFile *file = GetFileUnlocked(path); if (!file) { - LOG_S(WARNING) << "Could not change " << filename - << " because it was not open"; + LOG_S(WARNING) << "Could not change " << path << " because it was not open"; return; } + file->timestamp = chrono::duration_cast( + chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + // version: number | null if (change.textDocument.version) file->version = *change.textDocument.version; @@ -440,20 +438,9 @@ void WorkingFiles::OnChange(const TextDocumentDidChangeParam &change) { } } -void WorkingFiles::OnClose(const TextDocumentIdentifier &close) { - std::lock_guard lock(files_mutex); - - std::string filename = close.uri.GetPath(); - - for (int i = 0; i < files.size(); ++i) { - if (files[i]->filename == filename) { - files.erase(files.begin() + i); - return; - } - } - - LOG_S(WARNING) << "Could not close " << filename - << " because it was not open"; +void WorkingFiles::OnClose(const std::string &path) { + std::lock_guard lock(mutex); + files.erase(path); } // VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent diff --git a/src/working_files.hh b/src/working_files.hh index 95cdf957..76128254 100644 --- a/src/working_files.hh +++ b/src/working_files.hh @@ -21,9 +21,11 @@ limitations under the License. #include #include #include +#include namespace ccls { struct WorkingFile { + int64_t timestamp = 0; int version = 0; std::string filename; @@ -41,9 +43,7 @@ struct WorkingFile { std::vector index_to_buffer; std::vector buffer_to_index; // A set of diagnostics that have been reported for this file. - // NOTE: _ is appended because it must be accessed under the WorkingFiles - // lock! - std::vector diagnostics_; + std::vector diagnostics; WorkingFile(const std::string &filename, const std::string &buffer_content); @@ -79,38 +79,21 @@ private: }; struct WorkingFiles { - struct Snapshot { - struct File { - std::string filename; - std::string content; - }; + WorkingFile *GetFile(const std::string &path); + WorkingFile *GetFileUnlocked(const std::string &path); + std::string GetContent(const std::string &path); - std::vector files; - }; - - // - // :: IMPORTANT :: All methods in this class are guarded by a single lock. - // - - // Find the file with the given filename. - WorkingFile *GetFileByFilename(const std::string &filename); - WorkingFile *GetFileByFilenameNoLock(const std::string &filename); - std::string GetContent(const std::string &filename); - - // Run |action| under the lock. - template void DoAction(Fn &&fn) { - std::lock_guard lock(files_mutex); + template void WithLock(Fn &&fn) { + std::lock_guard lock(mutex); fn(); } WorkingFile *OnOpen(const TextDocumentItem &open); void OnChange(const TextDocumentDidChangeParam &change); - void OnClose(const TextDocumentIdentifier &close); + void OnClose(const std::string &close); - // Use unique_ptrs so we can handout WorkingFile ptrs and not have them - // invalidated if we resize files. - std::vector> files; - std::mutex files_mutex; // Protects |files|. + std::mutex mutex; + std::unordered_map> files; }; int GetOffsetForPosition(Position pos, std::string_view content);