#include "query_utils.h" #include "queue_manager.h" #include #include #include namespace { // Computes roughly how long |range| is. int ComputeRangeSize(const Range& range) { if (range.start.line != range.end.line) return INT_MAX; return range.end.column - range.start.column; } } // namespace Maybe GetDefinitionSpellingOfSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::File: break; case SymbolKind::Func: { if (const auto* def = db->GetFunc(sym).AnyDef()) return def->spell; break; } case SymbolKind::Type: { if (const auto* def = db->GetType(sym).AnyDef()) return def->spell; break; } case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(sym).AnyDef(); if (def) return def->spell; break; } case SymbolKind::Invalid: assert(false && "unexpected"); break; } return nullopt; } Maybe GetDefinitionExtentOfSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::File: return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind, Role::None, QueryFileId(sym.id)); case SymbolKind::Func: { if (const auto* def = db->GetFunc(sym).AnyDef()) return def->extent; break; } case SymbolKind::Type: { if (const auto* def = db->GetType(sym).AnyDef()) return def->extent; break; } case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(sym).AnyDef(); if (def) return def->extent; break; } case SymbolKind::Invalid: { assert(false && "unexpected"); break; } } return nullopt; } Maybe GetDeclarationFileForSymbol(QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::File: return QueryFileId(sym.id); case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (!func.declarations.empty()) return func.declarations[0].file; if (const auto* def = func.AnyDef()) return def->file; break; } case SymbolKind::Type: { if (const auto* def = db->GetType(sym).AnyDef()) return def->file; break; } case SymbolKind::Var: { if (const auto* def = db->GetVar(sym).AnyDef()) return def->file; break; } case SymbolKind::Invalid: { assert(false && "unexpected"); break; } } return nullopt; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryFunc& func = db->funcs[id.id]; const QueryFunc::Def* def = func.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); else if (func.declarations.size()) ret.push_back(func.declarations[0]); } return ret; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryType& type = db->types[id.id]; const QueryType::Def* def = type.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); } return ret; } std::vector ToUses(QueryDatabase* db, const std::vector& ids) { std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { QueryVar& var = db->vars[id.id]; const QueryVar::Def* def = var.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); else if (var.declarations.size()) ret.push_back(var.declarations[0]); } return ret; } std::vector GetUsesOfSymbol(QueryDatabase* db, SymbolIdx sym, bool include_decl) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); std::vector ret = type.uses; if (include_decl) { const QueryType::Def* def = type.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); } return ret; } case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); std::vector ret = func.uses; if (include_decl) { const QueryFunc::Def* def = func.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); AddRange(&ret, func.declarations); } return ret; } case SymbolKind::Var: { QueryVar& var = db->GetVar(sym); std::vector ret = var.uses; if (include_decl) { const QueryVar::Def* def = var.AnyDef(); if (def && def->spell) ret.push_back(*def->spell); ret.insert(ret.end(), var.declarations.begin(), var.declarations.end()); } return ret; } case SymbolKind::File: case SymbolKind::Invalid: { assert(false && "unexpected"); return {}; } } } std::vector GetDeclarationsOfSymbolForGotoDefinition( QueryDatabase* db, SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Type: { // Returning the definition spelling of a type is a hack (and is why the // function has the postfix `ForGotoDefintion`, but it lets the user // jump to the start of a type if clicking goto-definition on the same // type from within the type definition. if (const auto* def = db->GetType(sym).AnyDef()) { Maybe spell = def->spell; if (spell) return {*spell}; } break; } case SymbolKind::Func: return db->GetFunc(sym).declarations; case SymbolKind::Var: return db->GetVar(sym).declarations; default: break; } return {}; } bool HasCallersOnSelfOrBaseOrDerived(QueryDatabase* db, QueryFunc& root) { // Check self. if (!root.uses.empty()) return true; const QueryFunc::Def* def = root.AnyDef(); // Check for base calls. std::queue queue; EachWithGen(db->funcs, def->base, [&](QueryFunc& func) { queue.push(&func); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); if (!func.uses.empty()) return true; if (def) EachWithGen(db->funcs, def->base, [&](QueryFunc& func1) { queue.push(&func1); }); } // Check for derived calls. EachWithGen(db->funcs, root.derived, [&](QueryFunc& func1) { queue.push(&func1); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); if (!func.uses.empty()) return true; EachWithGen(db->funcs, func.derived, [&](QueryFunc& func1) { queue.push(&func1); }); } return false; } std::vector GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) { std::vector callers; const QueryFunc::Def* def = root.AnyDef(); if (!def) return callers; std::queue queue; EachWithGen(db->funcs, def->base, [&](QueryFunc& func1) { queue.push(&func1); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); AddRange(&callers, func.uses); if (def) EachWithGen(db->funcs, def->base, [&](QueryFunc& func1) { queue.push(&func1); }); } return callers; } std::vector GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) { std::vector callers; std::queue queue; EachWithGen(db->funcs, root.derived, [&](QueryFunc& func) { queue.push(&func); }); while (!queue.empty()) { QueryFunc& func = *queue.front(); queue.pop(); EachWithGen(db->funcs, func.derived, [&](QueryFunc& func1) { queue.push(&func1); }); AddRange(&callers, func.uses); } return callers; } optional GetLsPosition(WorkingFile* working_file, const Position& position) { if (!working_file) return lsPosition(position.line, position.column); int column = position.column; optional start = working_file->GetBufferPosFromIndexPos(position.line, &column, false); if (!start) return nullopt; return lsPosition(*start, column); } optional GetLsRange(WorkingFile* working_file, const Range& location) { if (!working_file) { return lsRange(lsPosition(location.start.line, location.start.column), lsPosition(location.end.line, location.end.column)); } int start_column = location.start.column, end_column = location.end.column; optional start = working_file->GetBufferPosFromIndexPos( location.start.line, &start_column, false); optional end = working_file->GetBufferPosFromIndexPos(location.end.line, &end_column, true); if (!start || !end) return nullopt; // If remapping end fails (end can never be < start), just guess that the // final location didn't move. This only screws up the highlighted code // region if we guess wrong, so not a big deal. // // Remapping fails often in C++ since there are a lot of "};" at the end of // class/struct definitions. if (*end < *start) *end = *start + (location.end.line - location.start.line); if (*start == *end && start_column > end_column) end_column = start_column; return lsRange(lsPosition(*start, start_column), lsPosition(*end, end_column)); } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path) { QueryFile& file = db->files[file_id.id]; if (file.def) { *path = file.def->path; return lsDocumentUri::FromPath(*path); } else { *path = ""; return lsDocumentUri::FromPath(""); } } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) { QueryFile& file = db->files[file_id.id]; if (file.def) { return lsDocumentUri::FromPath(file.def->path); } else { return lsDocumentUri::FromPath(""); } } optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, Use use) { std::string path; lsDocumentUri uri = GetLsDocumentUri(db, use.file, &path); optional range = GetLsRange(working_files->GetFileByFilename(path), use.range); if (!range) return nullopt; return lsLocation(uri, *range); } optional GetLsLocationEx(QueryDatabase* db, WorkingFiles* working_files, Use use, bool extension) { optional ls_loc = GetLsLocation(db, working_files, use); if (!ls_loc) return nullopt; lsLocationEx ret; ret.lsLocation::operator=(*ls_loc); if (extension) switch (use.kind) { default: break; case SymbolKind::Func: { const QueryFunc::Def* def = db->GetFunc(use).AnyDef(); if (def) ret.containerName = std::string_view(def->detailed_name); break; } case SymbolKind::Type: { const QueryType::Def* def = db->GetType(use).AnyDef(); if (def) ret.containerName = std::string_view(def->detailed_name); break; } case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(use).AnyDef(); if (def) ret.containerName = std::string_view(def->detailed_name); break; } } return ret; } std::vector GetLsLocations( QueryDatabase* db, WorkingFiles* working_files, const std::vector& uses) { std::vector ret; for (Use use : uses) { optional location = GetLsLocation(db, working_files, use); if (location) ret.push_back(*location); } std::sort(ret.begin(), ret.end()); ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); return ret; } // Returns a symbol. The symbol will have *NOT* have a location assigned. optional GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx sym, bool use_short_name) { switch (sym.kind) { case SymbolKind::File: { QueryFile& file = db->GetFile(sym); if (!file.def) break; lsSymbolInformation info; info.name = file.def->path; info.kind = lsSymbolKind::File; return info; } case SymbolKind::Type: { const QueryType::Def* def = db->GetType(sym).AnyDef(); if (!def) break; lsSymbolInformation info; if (use_short_name) info.name = def->ShortName(); else info.name = def->detailed_name; if (def->detailed_name.c_str() != def->ShortName()) info.containerName = def->detailed_name; // TODO ClangSymbolKind -> lsSymbolKind switch (def->kind) { default: info.kind = lsSymbolKind::Class; break; case ClangSymbolKind::Namespace: info.kind = lsSymbolKind::Namespace; break; } return info; } case SymbolKind::Func: { const QueryFunc::Def* def = db->GetFunc(sym).AnyDef(); if (!def) break; lsSymbolInformation info; if (use_short_name) info.name = def->ShortName(); else info.name = def->detailed_name; info.containerName = def->detailed_name; switch (def->kind) { default: info.kind = lsSymbolKind::Function; break; case ClangSymbolKind::InstanceMethod: case ClangSymbolKind::StaticMethod: info.kind = lsSymbolKind::Method; break; } return info; } case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(sym).AnyDef(); if (!def) break; lsSymbolInformation info; if (use_short_name) info.name = def->ShortName(); else info.name = def->detailed_name; info.containerName = def->detailed_name; switch (def->kind) { default: info.kind = lsSymbolKind::Variable; break; case ClangSymbolKind::EnumConstant: info.kind = lsSymbolKind::EnumMember; break; } return info; } case SymbolKind::Invalid: break; } return nullopt; } // TODO Sort only by range length, not |kind| or |idx| std::vector FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) { std::vector symbols; symbols.reserve(1); int target_line = position.line; int target_column = position.character; if (working_file) { optional index_line = working_file->GetIndexPosFromBufferPos( target_line, &target_column, false); if (index_line) target_line = *index_line; } for (const SymbolRef& sym : file->def->all_symbols) { if (sym.range.Contains(target_line, target_column)) symbols.push_back(sym); } // Order shorter ranges first, since they are more detailed/precise. This is // important for macros which generate code so that we can resolving the // macro argument takes priority over the entire macro body. // // Order SymbolKind::Var before SymbolKind::Type. Macro calls are treated as // Var currently. If a macro expands to tokens led by a SymbolKind::Type, the // macro and the Type have the same range. We want to find the macro // definition instead of the Type definition. // // Then order functions before other types, which makes goto definition work // better on constructors. std::sort(symbols.begin(), symbols.end(), [](const SymbolRef& a, const SymbolRef& b) { int a_size = ComputeRangeSize(a.range); int b_size = ComputeRangeSize(b.range); if (a_size != b_size) return a_size < b_size; // operator> orders Var/Func before Type. int t = static_cast(a.kind) - static_cast(b.kind); if (t) return t > 0; return a.id < b.id; }); return symbols; } void EmitDiagnostics(WorkingFiles* working_files, std::string path, std::vector diagnostics) { // Emit diagnostics. Out_TextDocumentPublishDiagnostics out; out.params.uri = lsDocumentUri::FromPath(path); out.params.diagnostics = diagnostics; QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); // Cache diagnostics so we can show fixits. working_files->DoActionOnFile(path, [&](WorkingFile* working_file) { if (working_file) working_file->diagnostics_ = diagnostics; }); }