diff --git a/src/config.h b/src/config.h index 23b2fdb8d..1b7b7ae57 100644 --- a/src/config.h +++ b/src/config.h @@ -81,6 +81,8 @@ struct Config { } clang; struct ClientCapability { + // TextDocumentClientCapabilities.documentSymbol.hierarchicalDocumentSymbolSupport + bool hierarchicalDocumentSymbolSupport = false; // TextDocumentClientCapabilities.completion.completionItem.snippetSupport bool snippetSupport = false; } client; @@ -249,7 +251,8 @@ struct Config { }; MAKE_REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings, resourceDir); -MAKE_REFLECT_STRUCT(Config::ClientCapability, snippetSupport); +MAKE_REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport, + snippetSupport); MAKE_REFLECT_STRUCT(Config::CodeLens, localVariables); MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel, dropOldRequests, duplicateOptional, filterAndSort, diff --git a/src/indexer.cc b/src/indexer.cc index cc8fc8bcb..e4c86d312 100644 --- a/src/indexer.cc +++ b/src/indexer.cc @@ -904,22 +904,23 @@ class IndexDataConsumer : public index::IndexDataConsumer { type->def.kind = RD->getTagKind() == TTK_Struct ? lsSymbolKind::Struct : lsSymbolKind::Class; if (type->def.detailed_name[0] == '\0' && info->short_name.empty()) { + StringRef Tag; + switch (RD->getTagKind()) { + case TTK_Struct: Tag = "struct"; break; + case TTK_Interface: Tag = "__interface"; break; + case TTK_Union: Tag = "union"; break; + case TTK_Class: Tag = "class"; break; + case TTK_Enum: Tag = "enum"; break; + } if (TypedefNameDecl *TD = RD->getTypedefNameForAnonDecl()) { StringRef Name = TD->getName(); - StringRef Tag; - switch (RD->getTagKind()) { - case TTK_Struct: Tag = "struct "; break; - case TTK_Interface: Tag = "__interface "; break; - case TTK_Union: Tag = "union "; break; - case TTK_Class: Tag = "class "; break; - case TTK_Enum: Tag = "enum "; break; - } - std::string name = ("anon " + Tag + Name).str(); + std::string name = ("anon " + Tag + " " + Name).str(); type->def.detailed_name = Intern(name); type->def.short_name_size = name.size(); } else { - // e.g. "struct {}" - SetName(OrigD, "", "", type->def); + std::string name = ("anon " + Tag).str(); + type->def.detailed_name = Intern(name); + type->def.short_name_size = name.size(); } } if (is_def) { diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index f93c04943..407f7b184 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -270,6 +270,10 @@ struct lsTextDocumentClientCapabilities { } completionItem; } completion; + struct lsDocumentSymbol { + bool hierarchicalDocumentSymbolSupport = false; + } documentSymbol; + struct lsGenericDynamicReg { // Whether foo supports dynamic registration. std::optional dynamicRegistration; @@ -291,6 +295,8 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsSynchronization, dynamicRegistration, willSave, willSaveWaitUntil, didSave); MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsCompletion, dynamicRegistration, completionItem); +MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsDocumentSymbol, + hierarchicalDocumentSymbolSupport); MAKE_REFLECT_STRUCT( lsTextDocumentClientCapabilities::lsCompletion::lsCompletionItem, snippetSupport); @@ -299,8 +305,8 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsGenericDynamicReg, MAKE_REFLECT_STRUCT( lsTextDocumentClientCapabilities::CodeLensRegistrationOptions, dynamicRegistration, resolveProvider); -MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities, synchronization, - completion, rename); +MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities, completion, + documentSymbol, rename, synchronization); struct lsClientCapabilities { // Workspace specific client capabilities. @@ -449,6 +455,8 @@ struct Handler_Initialize : BaseMessageHandler { const auto &capabilities = params.capabilities; g_config->client.snippetSupport = capabilities.textDocument.completion.completionItem.snippetSupport; + g_config->client.hierarchicalDocumentSymbolSupport = + capabilities.textDocument.documentSymbol.hierarchicalDocumentSymbolSupport; // Ensure there is a resource directory. if (g_config->clang.resourceDir.empty()) diff --git a/src/messages/textDocument_documentSymbol.cc b/src/messages/textDocument_documentSymbol.cc index 9991cc931..f6064d196 100644 --- a/src/messages/textDocument_documentSymbol.cc +++ b/src/messages/textDocument_documentSymbol.cc @@ -19,6 +19,8 @@ limitations under the License. using namespace ccls; using namespace clang; +MAKE_HASHABLE(SymbolIdx, t.usr, t.kind); + namespace { MethodType kMethodType = "textDocument/documentSymbol"; @@ -52,6 +54,28 @@ struct Out_TextDocumentDocumentSymbol }; MAKE_REFLECT_STRUCT(Out_TextDocumentDocumentSymbol, jsonrpc, id, result); +struct lsDocumentSymbol { + std::string name; + std::string detail; + lsSymbolKind kind; + lsRange range; + lsRange selectionRange; + std::vector> children; +}; +void Reflect(Writer &vis, std::unique_ptr &v); +MAKE_REFLECT_STRUCT(lsDocumentSymbol, name, detail, kind, range, selectionRange, + children); +void Reflect(Writer &vis, std::unique_ptr &v) { + Reflect(vis, *v); +} + +struct Out_HierarchicalDocumentSymbol + : public lsOutMessage { + lsRequestId id; + std::vector> result; +}; +MAKE_REFLECT_STRUCT(Out_HierarchicalDocumentSymbol, jsonrpc, id, result); + struct Handler_TextDocumentDocumentSymbol : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } @@ -63,6 +87,9 @@ struct Handler_TextDocumentDocumentSymbol if (!FindFileOrFail(db, project, request->id, params.textDocument.uri.GetPath(), &file, &file_id)) return; + WorkingFile *wfile = working_files->GetFileByFilename(file->def->path); + if (!wfile) + return; const auto &symbol2refcnt = params.all ? file->symbol2refcnt : file->outline2refcnt; @@ -78,6 +105,92 @@ struct Handler_TextDocumentDocumentSymbol out.result.push_back(ls_loc->range); std::sort(out.result.begin(), out.result.end()); pipeline::WriteStdout(kMethodType, out); + } else if (g_config->client.hierarchicalDocumentSymbolSupport) { + std::unordered_map< + SymbolIdx, std::pair>> + sym2ds; + for (auto [sym, refcnt] : symbol2refcnt) { + if (refcnt <= 0) + continue; + auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind}); + if (!r.second) + continue; + auto &kv = r.first->second; + kv.second = std::make_unique(); + lsDocumentSymbol &ds = *kv.second; + WithEntity(db, sym, [&](const auto &entity) { + auto *def = entity.AnyDef(); + if (!def) + return; + ds.name = def->Name(false); + ds.detail = def->Name(true); + for (auto &def : entity.def) + if (def.file_id == file_id) { + if (!def.spell || !def.extent) + break; + ds.kind = def.kind; + if (auto ls_range = GetLsRange(wfile, def.extent->range)) + ds.range = *ls_range; + else + break; + if (auto ls_range = GetLsRange(wfile, def.spell->range)) + ds.selectionRange = *ls_range; + else + break; + kv.first = static_cast(&def); + } + }); + if (kv.first && sym.kind == SymbolKind::Var) + if (static_cast(kv.first)->is_local()) + kv.first = nullptr; + if (!kv.first) { + kv.second.reset(); + continue; + } + } + for (auto &[sym, def_ds] : sym2ds) { + if (!def_ds.second) + continue; + lsDocumentSymbol &ds = *def_ds.second; + switch (sym.kind) { + case SymbolKind::Func: { + auto &def = *static_cast(def_ds.first); + for (Usr usr1 : def.vars) { + auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var}); + if (it != sym2ds.end() && it->second.second) + ds.children.push_back(std::move(it->second.second)); + } + break; + } + case SymbolKind::Type: { + auto &def = *static_cast(def_ds.first); + for (Usr usr1 : def.funcs) { + auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Func}); + if (it != sym2ds.end() && it->second.second) + ds.children.push_back(std::move(it->second.second)); + } + for (Usr usr1 : def.types) { + auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Type}); + if (it != sym2ds.end() && it->second.second) + ds.children.push_back(std::move(it->second.second)); + } + for (auto [usr1, _] : def.vars) { + auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var}); + if (it != sym2ds.end() && it->second.second) + ds.children.push_back(std::move(it->second.second)); + } + break; + } + default: + break; + } + } + Out_HierarchicalDocumentSymbol out; + out.id = request->id; + for (auto &[sym, def_ds] : sym2ds) + if (def_ds.second) + out.result.push_back(std::move(def_ds.second)); + pipeline::WriteStdout(kMethodType, out); } else { Out_TextDocumentDocumentSymbol out; out.id = request->id; diff --git a/src/messages/workspace_didChangeWatchedFiles.cc b/src/messages/workspace_didChangeWatchedFiles.cc index 8ba464083..fd5806d16 100644 --- a/src/messages/workspace_didChangeWatchedFiles.cc +++ b/src/messages/workspace_didChangeWatchedFiles.cc @@ -54,28 +54,22 @@ struct Handler_WorkspaceDidChangeWatchedFiles void Run(In_WorkspaceDidChangeWatchedFiles *request) override { for (lsFileEvent &event : request->params.changes) { std::string path = event.uri.GetPath(); - Project::Entry entry; - { - std::lock_guard lock(project->mutex_); - auto it = project->path_to_entry_index.find(path); - if (it == project->path_to_entry_index.end()) - continue; - entry = project->entries[it->second]; - } - IndexMode mode = - working_files->GetFileByFilename(entry.filename) != nullptr - ? IndexMode::Normal - : IndexMode::NonInteractive; + IndexMode mode = working_files->GetFileByFilename(path) + ? IndexMode::Normal + : IndexMode::NonInteractive; switch (event.type) { case lsFileChangeType::Created: case lsFileChangeType::Changed: { - pipeline::Index(path, entry.args, mode); + pipeline::Index(path, {}, mode); if (mode == IndexMode::Normal) clang_complete->NotifySave(path); + else + clang_complete->FlushSession(path); break; } case lsFileChangeType::Deleted: - pipeline::Index(path, entry.args, mode); + pipeline::Index(path, {}, mode); + clang_complete->FlushSession(path); break; } }