// Copyright 2017-2018 ccls Authors // SPDX-License-Identifier: Apache-2.0 #include "message_handler.hh" #include "pipeline.hh" #include "query.hh" #include #include 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; }; REFLECT_STRUCT(DocumentSymbol, name, detail, kind, range, selectionRange, children); 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()); /** * with 2 ranges that start at the same Position, we want the wider one * first (swap lhs/rhs) * * we use range for start to start at symbol name * issue with: int i, j, k; otherwise */ auto sym_cmp = [](ExtentRef const &lhs, ExtentRef const &rhs) { return lhs.range.start < rhs.range.start || (lhs.range.start == rhs.range.start && rhs.extent.end < lhs.extent.end); }; for (auto [sym, refcnt] : file->symbol2refcnt) { if (refcnt <= 0 || !sym.extent.valid()) continue; syms.push_back(sym); } std::sort(std::begin(syms), std::end(syms), sym_cmp); std::vector result; std::stack indent; for (auto sym : syms) { DocumentSymbol ds; 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) { auto const *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 symbols that are behind the current one while (!indent.empty() && // use selectionRange for start to start at symbol name // issue with: int i, j, k; otherwise indent.top()->range.end < ds.selectionRange.start) { indent.pop(); } if (indent.empty()) { result.push_back(std::move(ds)); indent.push(&result.back()); } else { indent.top()->children.push_back(std::move(ds)); indent.push(&indent.top()->children.back()); } } }); } reply(result); } 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); } } } // namespace ccls