// Copyright 2017-2018 ccls Authors // SPDX-License-Identifier: Apache-2.0 #include "message_handler.hh" #include "pipeline.hh" #include "project.hh" #include "query.hh" #include #include #include #include using namespace llvm; MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind); namespace ccls { REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); namespace { struct DocumentHighlight { enum Kind { Text = 1, Read = 2, Write = 3 }; lsRange range; int kind = 1; // ccls extension Role role = Role::None; bool operator<(const DocumentHighlight &o) const { return !(range == o.range) ? range < o.range : kind < o.kind; } }; REFLECT_STRUCT(DocumentHighlight, range, kind, role); } // namespace void MessageHandler::textDocument_documentHighlight( TextDocumentPositionParam ¶m, ReplyOnce &reply) { int file_id; auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id); if (!wf) return; std::vector result; std::vector syms = findSymbolsAtLocation(wf, file, param.position, true); for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0) continue; Usr usr = sym.usr; Kind kind = sym.kind; if (std::none_of(syms.begin(), syms.end(), [&](auto &sym1) { return usr == sym1.usr && kind == sym1.kind; })) continue; if (auto loc = getLsLocation(db, wfiles, sym, file_id)) { DocumentHighlight highlight; highlight.range = loc->range; if (sym.role & Role::Write) highlight.kind = DocumentHighlight::Write; else if (sym.role & Role::Read) highlight.kind = DocumentHighlight::Read; else highlight.kind = DocumentHighlight::Text; highlight.role = sym.role; result.push_back(highlight); } } std::sort(result.begin(), result.end()); reply(result); } namespace { struct DocumentLink { lsRange range; DocumentUri target; }; REFLECT_STRUCT(DocumentLink, range, target); } // namespace void MessageHandler::textDocument_documentLink(TextDocumentParam ¶m, ReplyOnce &reply) { auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply); if (!wf) return; std::vector result; int column; for (const IndexInclude &include : file->def->includes) if (std::optional bline = wf->getBufferPosFromIndexPos(include.line, &column, false)) { const std::string &line = wf->buffer_lines[*bline]; auto start = line.find_first_of("\"<"), end = line.find_last_of("\">"); if (start < end) result.push_back({lsRange{{*bline, (int)start + 1}, {*bline, (int)end}}, DocumentUri::fromPath(include.resolved_path)}); } reply(result); } // namespace ccls namespace { struct DocumentSymbolParam : TextDocumentParam { // Include sym if `!(sym.role & excludeRole)`. Role excludeRole = Role((int)Role::All - (int)Role::Definition - (int)Role::Declaration - (int)Role::Dynamic); // If >= 0, return Range[] instead of SymbolInformation[] to reduce output. int startLine = -1; int endLine = -1; }; REFLECT_STRUCT(DocumentSymbolParam, textDocument, excludeRole, startLine, endLine); struct DocumentSymbol { std::string name; std::string detail; SymbolKind kind; lsRange range; lsRange selectionRange; std::vector> children; }; void reflect(JsonWriter &vis, std::unique_ptr &v); REFLECT_STRUCT(DocumentSymbol, name, detail, kind, range, selectionRange, children); void reflect(JsonWriter &vis, std::unique_ptr &v) { reflect(vis, *v); } template bool ignore(const Def *def) { return false; } template <> bool ignore(const QueryType::Def *def) { return !def || def->kind == SymbolKind::TypeParameter; } template <> bool ignore(const QueryVar::Def *def) { return !def || def->is_local(); } } // namespace void MessageHandler::textDocument_documentSymbol(JsonReader &reader, ReplyOnce &reply) { DocumentSymbolParam param; reflect(reader, param); int file_id; auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id, true); if (!file) return; auto allows = [&](SymbolRef sym) { return !(sym.role & param.excludeRole); }; if (param.startLine >= 0) { std::vector result; for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0 || !allows(sym) || !(param.startLine <= sym.range.start.line && sym.range.start.line <= param.endLine)) continue; if (auto loc = getLsLocation(db, wfiles, sym, file_id)) result.push_back(loc->range); } std::sort(result.begin(), result.end()); reply(result); } else if (g_config->client.hierarchicalDocumentSymbolSupport) { std::vector syms; syms.reserve(file->symbol2refcnt.size()); for (auto [sym, refcnt] : file->symbol2refcnt) if (refcnt > 0 && sym.extent.valid()) syms.push_back(sym); // Global variables `int i, j, k;` have the same extent.start. Sort them by // range.start instead. In case of a tie, prioritize the widest ExtentRef. std::sort(syms.begin(), syms.end(), [](const ExtentRef &lhs, const ExtentRef &rhs) { return std::tie(lhs.range.start, rhs.extent.end) < std::tie(rhs.range.start, lhs.extent.end); }); std::vector> res; std::vector scopes; for (ExtentRef sym : syms) { auto ds = std::make_unique(); 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(wf, sym.extent); range1 && range1->includes(*range)) ds->range = *range1; } withEntity(db, sym, [&](const auto &entity) { const auto *def = entity.anyDef(); if (!def) return; ds->name = def->name(false); ds->detail = def->detailed_name; ds->kind = def->kind; if (!ignore(def) && (ds->kind == SymbolKind::Namespace || allows(sym))) { // Drop scopes which are before selectionRange.start. In // `int i, j, k;`, the scope of i will be ended by j. while (!scopes.empty() && scopes.back()->range.end <= ds->selectionRange.start) scopes.pop_back(); auto *ds1 = ds.get(); if (scopes.empty()) res.push_back(std::move(ds)); else scopes.back()->children.push_back(std::move(ds)); scopes.push_back(ds1); } }); } reply(res); } else { std::vector result; for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0 || !allows(sym)) continue; if (std::optional info = getSymbolInfo(db, sym, false)) { if ((sym.kind == Kind::Type && ignore(db->getType(sym).anyDef())) || (sym.kind == Kind::Var && ignore(db->getVar(sym).anyDef()))) continue; if (auto loc = getLsLocation(db, wfiles, sym, file_id)) { info->location = *loc; result.push_back(*info); } } } reply(result); } } void MessageHandler::textDocument_switchSourceHeader(TextDocumentIdentifier ¶m, ReplyOnce &reply) { QueryFile *file; WorkingFile *wf; std::tie(file, wf) = findOrFail(param.uri.getPath(), reply); if (!wf) return reply(JsonNull{}); int file_id = file->id; DocumentUri result; const std::string &path = wf->filename; bool is_hdr = lookupExtension(path).second; // Vote for each interesting symbol's definitions (for header) or declarations (for non-header). // Select the file with the most votes. // Ignore Type symbols to skip class forward declarations and namespaces. std::unordered_map file_id2cnt; for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0 || !sym.extent.valid() || sym.kind == Kind::Type) continue; if (is_hdr) { withEntity(db, sym, [&](const auto &entity) { for (auto &def : entity.def) if (def.spell && def.file_id != file_id) ++file_id2cnt[def.file_id]; }); } else { for (DeclRef dr : getNonDefDeclarations(db, sym)) if (dr.file_id != file_id) ++file_id2cnt[dr.file_id]; } } if (file_id2cnt.size()) { auto best = file_id2cnt.begin(); for (auto it = file_id2cnt.begin(); it != file_id2cnt.end(); ++it) if (it->second > best->second || (it->second == best->second && it->first < best->first)) best = it; if (auto &def = db->files[best->first].def) return reply(DocumentUri::fromPath(def->path)); } if (is_hdr) { // Check if `path` is in a #include entry. for (QueryFile &file1 : db->files) { auto &def = file1.def; if (!def || lookupExtension(def->path).second) continue; for (IndexInclude &include : def->includes) if (path == include.resolved_path) return reply(DocumentUri::fromPath(def->path)); } return reply(JsonNull{}); } // Otherwise, find the #include with the same stem. StringRef stem = sys::path::stem(path); for (IndexInclude &include : file->def->includes) if (sys::path::stem(include.resolved_path) == stem) return reply(DocumentUri::fromPath(std::string(include.resolved_path))); reply(JsonNull{}); } } // namespace ccls