mirror of
https://github.com/MaskRay/ccls.git
synced 2025-02-16 13:48:04 +00:00
Move some code out of command_line.cc
This commit is contained in:
parent
7328d138e3
commit
7a71447273
@ -7,7 +7,9 @@
|
||||
#include "ipc_manager.h"
|
||||
#include "indexer.h"
|
||||
#include "query.h"
|
||||
#include "query_utils.h"
|
||||
#include "language_server_api.h"
|
||||
#include "lex_utils.h"
|
||||
#include "options.h"
|
||||
#include "project.h"
|
||||
#include "platform.h"
|
||||
@ -125,40 +127,6 @@ bool ShouldDisplayIpcTiming(IpcId id) {
|
||||
|
||||
|
||||
|
||||
bool ShouldRunIncludeCompletion(const std::string& line) {
|
||||
size_t start = 0;
|
||||
while (start < line.size() && isspace(line[start]))
|
||||
++start;
|
||||
return start < line.size() && line[start] == '#';
|
||||
}
|
||||
|
||||
// TODO: eliminate |line_number| param.
|
||||
optional<lsRange> ExtractQuotedRange(int line_number, const std::string& line) {
|
||||
// Find starting and ending quote.
|
||||
int start = 0;
|
||||
while (start < line.size()) {
|
||||
char c = line[start];
|
||||
++start;
|
||||
if (c == '"' || c == '<')
|
||||
break;
|
||||
}
|
||||
if (start == line.size())
|
||||
return nullopt;
|
||||
|
||||
int end = (int)line.size();
|
||||
while (end > 0) {
|
||||
char c = line[end];
|
||||
if (c == '"' || c == '>')
|
||||
break;
|
||||
--end;
|
||||
}
|
||||
|
||||
if (start >= end)
|
||||
return nullopt;
|
||||
|
||||
return lsRange(lsPosition(line_number, start), lsPosition(line_number, end));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -226,690 +194,6 @@ QueryFile* FindFile(QueryDatabase* db, const std::string& absolute_path) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id) {
|
||||
optional<QueryType>& type = db->types[id.id];
|
||||
if (type)
|
||||
return type->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id) {
|
||||
optional<QueryFunc>& func = db->funcs[id.id];
|
||||
if (func)
|
||||
return func->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id) {
|
||||
optional<QueryVar>& var = db->vars[id.id];
|
||||
if (var)
|
||||
return var->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File: {
|
||||
return QueryLocation(QueryFileId(symbol.idx), Range(Position(1, 1), Position(1, 1)));
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::string GetHoverForSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
optional<QueryFileId> GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type && type->def.definition_spelling)
|
||||
return type->def.definition_spelling->path;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func) {
|
||||
if (!func->declarations.empty())
|
||||
return func->declarations[0].path;
|
||||
if (func->def.definition_spelling)
|
||||
return func->def.definition_spelling->path;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var && var->def.definition_spelling)
|
||||
return var->def.definition_spelling->path;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File: {
|
||||
return QueryFileId(symbol.idx);
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncRef>& refs) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(refs.size());
|
||||
for (const QueryFuncRef& ref : refs)
|
||||
locs.push_back(ref.loc);
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryTypeId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryTypeId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryFuncId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryVarId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryVarId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<QueryLocation> GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->uses;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
// TODO: the vector allocation could be avoided.
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func) {
|
||||
std::vector<QueryLocation> result = ToQueryLocation(db, func->callers);
|
||||
AddRange(&result, func->declarations);
|
||||
if (func->def.definition_spelling)
|
||||
result.push_back(*func->def.definition_spelling);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->uses;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<QueryLocation> GetDeclarationsOfSymbolForGotoDefinition(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.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.
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type) {
|
||||
optional<QueryLocation> declaration = type->def.definition_spelling;
|
||||
if (declaration)
|
||||
return { *declaration };
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->declarations;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var) {
|
||||
optional<QueryLocation> declaration = var->def.declaration;
|
||||
if (declaration)
|
||||
return { *declaration };
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func) {
|
||||
if (!func.def.base)
|
||||
return nullopt;
|
||||
optional<QueryFunc>& base = db->funcs[func.def.base->id];
|
||||
if (!base)
|
||||
return nullopt;
|
||||
|
||||
auto def = base->def.definition_spelling;
|
||||
if (!def && !base->declarations.empty())
|
||||
def = base->declarations[0];
|
||||
return def;
|
||||
}
|
||||
|
||||
std::vector<QueryFuncRef> GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) {
|
||||
std::vector<QueryFuncRef> callers;
|
||||
|
||||
optional<QueryFuncId> func_id = root.def.base;
|
||||
while (func_id) {
|
||||
optional<QueryFunc>& func = db->funcs[func_id->id];
|
||||
if (!func)
|
||||
break;
|
||||
|
||||
AddRange(&callers, func->callers);
|
||||
func_id = func->def.base;
|
||||
}
|
||||
|
||||
return callers;
|
||||
}
|
||||
|
||||
std::vector<QueryFuncRef> GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) {
|
||||
std::vector<QueryFuncRef> callers;
|
||||
|
||||
std::queue<QueryFuncId> queue;
|
||||
PushRange(&queue, root.derived);
|
||||
|
||||
while (!queue.empty()) {
|
||||
optional<QueryFunc>& func = db->funcs[queue.front().id];
|
||||
queue.pop();
|
||||
if (!func)
|
||||
continue;
|
||||
|
||||
PushRange(&queue, func->derived);
|
||||
AddRange(&callers, func->callers);
|
||||
}
|
||||
|
||||
return callers;
|
||||
}
|
||||
|
||||
optional<lsPosition> GetLsPosition(WorkingFile* working_file, const Position& position) {
|
||||
if (!working_file)
|
||||
return lsPosition(position.line - 1, position.column - 1);
|
||||
|
||||
optional<int> start = working_file->GetBufferLineFromIndexLine(position.line);
|
||||
if (!start)
|
||||
return nullopt;
|
||||
|
||||
return lsPosition(*start - 1, position.column - 1);
|
||||
}
|
||||
|
||||
optional<lsRange> GetLsRange(WorkingFile* working_file, const Range& location) {
|
||||
if (!working_file) {
|
||||
return lsRange(
|
||||
lsPosition(location.start.line - 1, location.start.column - 1),
|
||||
lsPosition(location.end.line - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
optional<int> start = working_file->GetBufferLineFromIndexLine(location.start.line);
|
||||
optional<int> end = working_file->GetBufferLineFromIndexLine(location.end.line);
|
||||
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);
|
||||
|
||||
return lsRange(
|
||||
lsPosition(*start - 1, location.start.column - 1),
|
||||
lsPosition(*end - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path) {
|
||||
optional<QueryFile>& file = db->files[file_id.id];
|
||||
if (file) {
|
||||
*path = file->def.path;
|
||||
return lsDocumentUri::FromPath(*path);
|
||||
}
|
||||
else {
|
||||
*path = "";
|
||||
return lsDocumentUri::FromPath("");
|
||||
}
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) {
|
||||
optional<QueryFile>& file = db->files[file_id.id];
|
||||
if (file) {
|
||||
return lsDocumentUri::FromPath(file->def.path);
|
||||
}
|
||||
else {
|
||||
return lsDocumentUri::FromPath("");
|
||||
}
|
||||
}
|
||||
|
||||
optional<lsLocation> GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location) {
|
||||
std::string path;
|
||||
lsDocumentUri uri = GetLsDocumentUri(db, location.path, &path);
|
||||
optional<lsRange> range = GetLsRange(working_files->GetFileByFilename(path), location.range);
|
||||
if (!range)
|
||||
return nullopt;
|
||||
return lsLocation(uri, *range);
|
||||
}
|
||||
|
||||
NonElidedVector<lsLocation> GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations) {
|
||||
std::unordered_set<lsLocation> unique_locations;
|
||||
for (const QueryLocation& query_location : locations) {
|
||||
optional<lsLocation> location = GetLsLocation(db, working_files, query_location);
|
||||
if (!location)
|
||||
continue;
|
||||
unique_locations.insert(*location);
|
||||
}
|
||||
|
||||
NonElidedVector<lsLocation> result;
|
||||
result.reserve(unique_locations.size());
|
||||
result.assign(unique_locations.begin(), unique_locations.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a symbol. The symbol will have *NOT* have a location assigned.
|
||||
optional<lsSymbolInformation> GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::File: {
|
||||
optional<QueryFile>& file = db->files[symbol.idx];
|
||||
if (!file)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = file->def.path;
|
||||
info.kind = lsSymbolKind::File;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (!type)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = type->def.short_name;
|
||||
if (type->def.detailed_name != type->def.short_name)
|
||||
info.containerName = type->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Class;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (!func)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = func->def.short_name;
|
||||
info.containerName = func->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Function;
|
||||
|
||||
if (func->def.declaring_type.has_value()) {
|
||||
optional<QueryType>& container = db->types[func->def.declaring_type->id];
|
||||
if (container)
|
||||
info.kind = lsSymbolKind::Method;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (!var)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name += var->def.short_name;
|
||||
info.containerName = var->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Variable;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
return nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
struct CommonCodeLensParams {
|
||||
std::vector<TCodeLens>* result;
|
||||
QueryDatabase* db;
|
||||
WorkingFiles* working_files;
|
||||
WorkingFile* working_file;
|
||||
};
|
||||
|
||||
void AddCodeLens(
|
||||
const char* singular,
|
||||
const char* plural,
|
||||
CommonCodeLensParams* common,
|
||||
QueryLocation loc,
|
||||
const std::vector<QueryLocation>& uses,
|
||||
optional<QueryLocation> excluded,
|
||||
bool force_display) {
|
||||
TCodeLens code_lens;
|
||||
optional<lsRange> range = GetLsRange(common->working_file, loc.range);
|
||||
if (!range)
|
||||
return;
|
||||
code_lens.range = *range;
|
||||
code_lens.command = lsCommand<lsCodeLensCommandArguments>();
|
||||
code_lens.command->command = "cquery.showReferences";
|
||||
code_lens.command->arguments.uri = GetLsDocumentUri(common->db, loc.path);
|
||||
code_lens.command->arguments.position = code_lens.range.start;
|
||||
|
||||
// Add unique uses.
|
||||
std::unordered_set<lsLocation> unique_uses;
|
||||
for (const QueryLocation& use : uses) {
|
||||
if (excluded == use)
|
||||
continue;
|
||||
optional<lsLocation> location = GetLsLocation(common->db, common->working_files, use);
|
||||
if (!location)
|
||||
continue;
|
||||
unique_uses.insert(*location);
|
||||
}
|
||||
code_lens.command->arguments.locations.assign(unique_uses.begin(),
|
||||
unique_uses.end());
|
||||
|
||||
// User visible label
|
||||
size_t num_usages = unique_uses.size();
|
||||
code_lens.command->title = std::to_string(num_usages) + " ";
|
||||
if (num_usages == 1)
|
||||
code_lens.command->title += singular;
|
||||
else
|
||||
code_lens.command->title += plural;
|
||||
|
||||
if (force_display || unique_uses.size() > 0)
|
||||
common->result->push_back(code_lens);
|
||||
}
|
||||
|
||||
lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations, const std::string& new_text) {
|
||||
std::unordered_map<QueryFileId, lsTextDocumentEdit> path_to_edit;
|
||||
|
||||
for (auto& location : locations) {
|
||||
optional<lsLocation> ls_location = GetLsLocation(db, working_files, location);
|
||||
if (!ls_location)
|
||||
continue;
|
||||
|
||||
if (path_to_edit.find(location.path) == path_to_edit.end()) {
|
||||
path_to_edit[location.path] = lsTextDocumentEdit();
|
||||
|
||||
optional<QueryFile>& file = db->files[location.path.id];
|
||||
if (!file)
|
||||
continue;
|
||||
|
||||
const std::string& path = file->def.path;
|
||||
path_to_edit[location.path].textDocument.uri = lsDocumentUri::FromPath(path);
|
||||
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(path);
|
||||
if (working_file)
|
||||
path_to_edit[location.path].textDocument.version = working_file->version;
|
||||
}
|
||||
|
||||
lsTextEdit edit;
|
||||
edit.range = ls_location->range;
|
||||
edit.newText = new_text;
|
||||
|
||||
// vscode complains if we submit overlapping text edits.
|
||||
auto& edits = path_to_edit[location.path].edits;
|
||||
if (std::find(edits.begin(), edits.end(), edit) == edits.end())
|
||||
edits.push_back(edit);
|
||||
}
|
||||
|
||||
|
||||
lsWorkspaceEdit edit;
|
||||
for (const auto& changes : path_to_edit)
|
||||
edit.documentChanges.push_back(changes.second);
|
||||
return edit;
|
||||
}
|
||||
|
||||
std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) {
|
||||
std::vector<SymbolRef> symbols;
|
||||
symbols.reserve(1);
|
||||
|
||||
int target_line = position.line + 1;
|
||||
int target_column = position.character + 1;
|
||||
if (working_file) {
|
||||
optional<int> index_line = working_file->GetIndexLineFromBufferLine(target_line);
|
||||
if (index_line)
|
||||
target_line = *index_line;
|
||||
}
|
||||
|
||||
for (const SymbolRef& ref : file->def.all_symbols) {
|
||||
if (ref.loc.range.Contains(target_line, target_column))
|
||||
symbols.push_back(ref);
|
||||
}
|
||||
|
||||
// Order function symbols first. This makes goto definition work better when
|
||||
// used on a constructor.
|
||||
std::sort(symbols.begin(), symbols.end(), [](const SymbolRef& a, const SymbolRef& b) {
|
||||
if (a.idx.kind != b.idx.kind && a.idx.kind == SymbolKind::Func)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryTypeHierarchyTree::TypeEntry> BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root) {
|
||||
optional<QueryType>& root_type = db->types[root.id];
|
||||
if (!root_type)
|
||||
return {};
|
||||
|
||||
NonElidedVector<Out_CqueryTypeHierarchyTree::TypeEntry> parent_entries;
|
||||
parent_entries.reserve(root_type->def.parents.size());
|
||||
|
||||
for (QueryTypeId parent_id : root_type->def.parents) {
|
||||
optional<QueryType>& parent_type = db->types[parent_id.id];
|
||||
if (!parent_type)
|
||||
continue;
|
||||
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry parent_entry;
|
||||
parent_entry.name = parent_type->def.detailed_name;
|
||||
if (parent_type->def.definition_spelling)
|
||||
parent_entry.location = GetLsLocation(db, working_files, *parent_type->def.definition_spelling);
|
||||
parent_entry.children = BuildParentTypeHierarchy(db, working_files, parent_id);
|
||||
|
||||
parent_entries.push_back(parent_entry);
|
||||
}
|
||||
|
||||
return parent_entries;
|
||||
}
|
||||
|
||||
|
||||
optional<Out_CqueryTypeHierarchyTree::TypeEntry> BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id) {
|
||||
optional<QueryType>& root_type = db->types[root_id.id];
|
||||
if (!root_type)
|
||||
return nullopt;
|
||||
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry entry;
|
||||
|
||||
// Name and location.
|
||||
entry.name = root_type->def.detailed_name;
|
||||
if (root_type->def.definition_spelling)
|
||||
entry.location = GetLsLocation(db, working_files, *root_type->def.definition_spelling);
|
||||
|
||||
entry.children.reserve(root_type->derived.size());
|
||||
|
||||
// Base types.
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry base;
|
||||
base.name = "[[Base]]";
|
||||
base.location = entry.location;
|
||||
base.children = BuildParentTypeHierarchy(db, working_files, root_id);
|
||||
if (!base.children.empty())
|
||||
entry.children.push_back(base);
|
||||
|
||||
// Add derived.
|
||||
for (QueryTypeId derived : root_type->derived) {
|
||||
auto derived_entry = BuildTypeHierarchy(db, working_files, derived);
|
||||
if (derived_entry)
|
||||
entry.children.push_back(*derived_entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) {
|
||||
optional<QueryFunc>& root_func = db->funcs[root.id];
|
||||
if (!root_func)
|
||||
return {};
|
||||
if (!root_func->def.definition_spelling)
|
||||
return {};
|
||||
optional<lsLocation> def_loc = GetLsLocation(db, working_files, *root_func->def.definition_spelling);
|
||||
if (!def_loc)
|
||||
return {};
|
||||
|
||||
Out_CqueryCallTree::CallEntry entry;
|
||||
entry.name = root_func->def.short_name;
|
||||
entry.usr = root_func->def.usr;
|
||||
entry.location = *def_loc;
|
||||
entry.hasCallers = !root_func->callers.empty();
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> result;
|
||||
result.push_back(entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) {
|
||||
optional<QueryFunc>& root_func = db->funcs[root.id];
|
||||
if (!root_func)
|
||||
return {};
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> result;
|
||||
result.reserve(root_func->callers.size());
|
||||
for (QueryFuncRef caller : root_func->callers) {
|
||||
optional<lsLocation> call_location = GetLsLocation(db, working_files, caller.loc);
|
||||
if (!call_location)
|
||||
continue;
|
||||
|
||||
if (caller.has_id()) {
|
||||
optional<QueryFunc>& call_func = db->funcs[caller.id_.id];
|
||||
if (!call_func)
|
||||
continue;
|
||||
|
||||
Out_CqueryCallTree::CallEntry call_entry;
|
||||
call_entry.name = call_func->def.short_name;
|
||||
call_entry.usr = call_func->def.usr;
|
||||
call_entry.location = *call_location;
|
||||
call_entry.hasCallers = !call_func->callers.empty();
|
||||
result.push_back(call_entry);
|
||||
}
|
||||
else {
|
||||
// TODO: See if we can do a better job here. Need more information from
|
||||
// the indexer.
|
||||
Out_CqueryCallTree::CallEntry call_entry;
|
||||
call_entry.name = "Likely Constructor";
|
||||
call_entry.usr = "no_usr";
|
||||
call_entry.location = *call_location;
|
||||
call_entry.hasCallers = false;
|
||||
result.push_back(call_entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PublishInactiveLines(WorkingFile* working_file, const std::vector<Range>& inactive) {
|
||||
Out_CquerySetInactiveRegion out;
|
||||
@ -923,112 +207,6 @@ void PublishInactiveLines(WorkingFile* working_file, const std::vector<Range>& i
|
||||
}
|
||||
|
||||
|
||||
void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional<std::string> type_name, std::string* insert_text, int* newlines_after_name) {
|
||||
int name_start = GetOffsetForPosition(declaration_spelling, buffer_content);
|
||||
|
||||
bool parse_return_type = true;
|
||||
// We need to check if we have a return type (ctors and dtors do not).
|
||||
if (type_name) {
|
||||
int name_end = name_start;
|
||||
while (name_end < buffer_content.size()) {
|
||||
char c = buffer_content[name_end];
|
||||
if (isspace(c) || c == '(')
|
||||
break;
|
||||
++name_end;
|
||||
}
|
||||
|
||||
std::string func_name = buffer_content.substr(name_start, name_end - name_start);
|
||||
if (func_name == *type_name || func_name == ("~" + *type_name))
|
||||
parse_return_type = false;
|
||||
}
|
||||
|
||||
// We need to fetch the return type. This can get complex, ie,
|
||||
//
|
||||
// std::vector <int> foo();
|
||||
//
|
||||
int return_start = name_start;
|
||||
if (parse_return_type) {
|
||||
int paren_balance = 0;
|
||||
int angle_balance = 0;
|
||||
bool expect_token = true;
|
||||
while (return_start > 0) {
|
||||
char c = buffer_content[return_start - 1];
|
||||
if (paren_balance == 0 && angle_balance == 0) {
|
||||
if (isspace(c) && !expect_token) {
|
||||
break;
|
||||
}
|
||||
if (!isspace(c))
|
||||
expect_token = false;
|
||||
}
|
||||
|
||||
if (c == ')')
|
||||
++paren_balance;
|
||||
if (c == '(') {
|
||||
--paren_balance;
|
||||
expect_token = true;
|
||||
}
|
||||
|
||||
if (c == '>')
|
||||
++angle_balance;
|
||||
if (c == '<') {
|
||||
--angle_balance;
|
||||
expect_token = true;
|
||||
}
|
||||
|
||||
return_start -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to fetch the arguments. Just scan for the next ';'.
|
||||
*newlines_after_name = 0;
|
||||
int end = name_start;
|
||||
while (end < buffer_content.size()) {
|
||||
char c = buffer_content[end];
|
||||
if (c == ';')
|
||||
break;
|
||||
if (c == '\n')
|
||||
*newlines_after_name += 1;
|
||||
++end;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result += buffer_content.substr(return_start, name_start - return_start);
|
||||
if (type_name && !type_name->empty())
|
||||
result += *type_name + "::";
|
||||
result += buffer_content.substr(name_start, end - name_start);
|
||||
TrimEnd(result);
|
||||
result += " {\n}";
|
||||
*insert_text = result;
|
||||
}
|
||||
|
||||
std::string LexWordAroundPos(lsPosition position, const std::string& content) {
|
||||
int index = GetOffsetForPosition(position, content);
|
||||
|
||||
int start = index;
|
||||
int end = index;
|
||||
|
||||
while (start > 0) {
|
||||
char c = content[start - 1];
|
||||
if (isalnum(c) || c == '_') {
|
||||
--start;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while ((end + 1) < content.size()) {
|
||||
char c = content[end + 1];
|
||||
if (isalnum(c) || c == '_') {
|
||||
++end;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return content.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
optional<int> FindIncludeLine(const std::vector<std::string>& lines, const std::string& full_include_line) {
|
||||
//
|
||||
|
176
src/lex_utils.cc
Normal file
176
src/lex_utils.cc
Normal file
@ -0,0 +1,176 @@
|
||||
#include "lex_utils.h"
|
||||
|
||||
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
||||
int offset = 0;
|
||||
|
||||
int remaining_lines = position.line;
|
||||
while (remaining_lines > 0) {
|
||||
if (content[offset] == '\n')
|
||||
--remaining_lines;
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset + position.character;
|
||||
}
|
||||
|
||||
lsPosition CharPos(const std::string& search, char character, int character_offset) {
|
||||
lsPosition result;
|
||||
int index = 0;
|
||||
while (index < search.size()) {
|
||||
char c = search[index];
|
||||
if (c == character)
|
||||
break;
|
||||
if (c == '\n') {
|
||||
result.line += 1;
|
||||
result.character = 0;
|
||||
}
|
||||
else {
|
||||
result.character += 1;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
assert(index < search.size());
|
||||
result.character += character_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ShouldRunIncludeCompletion(const std::string& line) {
|
||||
size_t start = 0;
|
||||
while (start < line.size() && isspace(line[start]))
|
||||
++start;
|
||||
return start < line.size() && line[start] == '#';
|
||||
}
|
||||
|
||||
// TODO: eliminate |line_number| param.
|
||||
optional<lsRange> ExtractQuotedRange(int line_number, const std::string& line) {
|
||||
// Find starting and ending quote.
|
||||
int start = 0;
|
||||
while (start < line.size()) {
|
||||
char c = line[start];
|
||||
++start;
|
||||
if (c == '"' || c == '<')
|
||||
break;
|
||||
}
|
||||
if (start == line.size())
|
||||
return nullopt;
|
||||
|
||||
int end = (int)line.size();
|
||||
while (end > 0) {
|
||||
char c = line[end];
|
||||
if (c == '"' || c == '>')
|
||||
break;
|
||||
--end;
|
||||
}
|
||||
|
||||
if (start >= end)
|
||||
return nullopt;
|
||||
|
||||
return lsRange(lsPosition(line_number, start), lsPosition(line_number, end));
|
||||
}
|
||||
|
||||
void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional<std::string> type_name, std::string* insert_text, int* newlines_after_name) {
|
||||
int name_start = GetOffsetForPosition(declaration_spelling, buffer_content);
|
||||
|
||||
bool parse_return_type = true;
|
||||
// We need to check if we have a return type (ctors and dtors do not).
|
||||
if (type_name) {
|
||||
int name_end = name_start;
|
||||
while (name_end < buffer_content.size()) {
|
||||
char c = buffer_content[name_end];
|
||||
if (isspace(c) || c == '(')
|
||||
break;
|
||||
++name_end;
|
||||
}
|
||||
|
||||
std::string func_name = buffer_content.substr(name_start, name_end - name_start);
|
||||
if (func_name == *type_name || func_name == ("~" + *type_name))
|
||||
parse_return_type = false;
|
||||
}
|
||||
|
||||
// We need to fetch the return type. This can get complex, ie,
|
||||
//
|
||||
// std::vector <int> foo();
|
||||
//
|
||||
int return_start = name_start;
|
||||
if (parse_return_type) {
|
||||
int paren_balance = 0;
|
||||
int angle_balance = 0;
|
||||
bool expect_token = true;
|
||||
while (return_start > 0) {
|
||||
char c = buffer_content[return_start - 1];
|
||||
if (paren_balance == 0 && angle_balance == 0) {
|
||||
if (isspace(c) && !expect_token) {
|
||||
break;
|
||||
}
|
||||
if (!isspace(c))
|
||||
expect_token = false;
|
||||
}
|
||||
|
||||
if (c == ')')
|
||||
++paren_balance;
|
||||
if (c == '(') {
|
||||
--paren_balance;
|
||||
expect_token = true;
|
||||
}
|
||||
|
||||
if (c == '>')
|
||||
++angle_balance;
|
||||
if (c == '<') {
|
||||
--angle_balance;
|
||||
expect_token = true;
|
||||
}
|
||||
|
||||
return_start -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to fetch the arguments. Just scan for the next ';'.
|
||||
*newlines_after_name = 0;
|
||||
int end = name_start;
|
||||
while (end < buffer_content.size()) {
|
||||
char c = buffer_content[end];
|
||||
if (c == ';')
|
||||
break;
|
||||
if (c == '\n')
|
||||
*newlines_after_name += 1;
|
||||
++end;
|
||||
}
|
||||
|
||||
std::string result;
|
||||
result += buffer_content.substr(return_start, name_start - return_start);
|
||||
if (type_name && !type_name->empty())
|
||||
result += *type_name + "::";
|
||||
result += buffer_content.substr(name_start, end - name_start);
|
||||
TrimEnd(result);
|
||||
result += " {\n}";
|
||||
*insert_text = result;
|
||||
}
|
||||
|
||||
std::string LexWordAroundPos(lsPosition position, const std::string& content) {
|
||||
int index = GetOffsetForPosition(position, content);
|
||||
|
||||
int start = index;
|
||||
int end = index;
|
||||
|
||||
while (start > 0) {
|
||||
char c = content[start - 1];
|
||||
if (isalnum(c) || c == '_') {
|
||||
--start;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while ((end + 1) < content.size()) {
|
||||
char c = content[end + 1];
|
||||
if (isalnum(c) || c == '_') {
|
||||
++end;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return content.substr(start, end - start + 1);
|
||||
}
|
20
src/lex_utils.h
Normal file
20
src/lex_utils.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "language_server_api.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
// Utility method to map |position| to an offset inside of |content|.
|
||||
int GetOffsetForPosition(lsPosition position, const std::string& content);
|
||||
// Utility method to find a position for the given character.
|
||||
lsPosition CharPos(const std::string& search, char character, int character_offset = 0);
|
||||
|
||||
bool ShouldRunIncludeCompletion(const std::string& line);
|
||||
|
||||
// TODO: eliminate |line_number| param.
|
||||
optional<lsRange> ExtractQuotedRange(int line_number, const std::string& line);
|
||||
|
||||
void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional<std::string> type_name, std::string* insert_text, int* newlines_after_name);
|
||||
|
||||
std::string LexWordAroundPos(lsPosition position, const std::string& content);
|
679
src/query_utils.cc
Normal file
679
src/query_utils.cc
Normal file
@ -0,0 +1,679 @@
|
||||
#include "query_utils.h"
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id) {
|
||||
optional<QueryType>& type = db->types[id.id];
|
||||
if (type)
|
||||
return type->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id) {
|
||||
optional<QueryFunc>& func = db->funcs[id.id];
|
||||
if (func)
|
||||
return func->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id) {
|
||||
optional<QueryVar>& var = db->vars[id.id];
|
||||
if (var)
|
||||
return var->def.definition_spelling;
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.definition_spelling;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.definition_extent;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File: {
|
||||
return QueryLocation(QueryFileId(symbol.idx), Range(Position(1, 1), Position(1, 1)));
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::string GetHoverForSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->def.detailed_name;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
optional<QueryFileId> GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type && type->def.definition_spelling)
|
||||
return type->def.definition_spelling->path;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func) {
|
||||
if (!func->declarations.empty())
|
||||
return func->declarations[0].path;
|
||||
if (func->def.definition_spelling)
|
||||
return func->def.definition_spelling->path;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var && var->def.definition_spelling)
|
||||
return var->def.definition_spelling->path;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File: {
|
||||
return QueryFileId(symbol.idx);
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncRef>& refs) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(refs.size());
|
||||
for (const QueryFuncRef& ref : refs)
|
||||
locs.push_back(ref.loc);
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryTypeId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryTypeId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryFuncId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryVarId>& ids) {
|
||||
std::vector<QueryLocation> locs;
|
||||
locs.reserve(ids.size());
|
||||
for (const QueryVarId& id : ids) {
|
||||
optional<QueryLocation> loc = GetDefinitionSpellingOfSymbol(db, id);
|
||||
if (loc)
|
||||
locs.push_back(loc.value());
|
||||
}
|
||||
return locs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::vector<QueryLocation> GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type)
|
||||
return type->uses;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
// TODO: the vector allocation could be avoided.
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func) {
|
||||
std::vector<QueryLocation> result = ToQueryLocation(db, func->callers);
|
||||
AddRange(&result, func->declarations);
|
||||
if (func->def.definition_spelling)
|
||||
result.push_back(*func->def.definition_spelling);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var)
|
||||
return var->uses;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::File:
|
||||
case SymbolKind::Invalid: {
|
||||
assert(false && "unexpected");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<QueryLocation> GetDeclarationsOfSymbolForGotoDefinition(QueryDatabase* db, const SymbolIdx& symbol) {
|
||||
switch (symbol.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.
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (type) {
|
||||
optional<QueryLocation> declaration = type->def.definition_spelling;
|
||||
if (declaration)
|
||||
return { *declaration };
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (func)
|
||||
return func->declarations;
|
||||
break;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (var) {
|
||||
optional<QueryLocation> declaration = var->def.declaration;
|
||||
if (declaration)
|
||||
return { *declaration };
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
optional<QueryLocation> GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func) {
|
||||
if (!func.def.base)
|
||||
return nullopt;
|
||||
optional<QueryFunc>& base = db->funcs[func.def.base->id];
|
||||
if (!base)
|
||||
return nullopt;
|
||||
|
||||
auto def = base->def.definition_spelling;
|
||||
if (!def && !base->declarations.empty())
|
||||
def = base->declarations[0];
|
||||
return def;
|
||||
}
|
||||
|
||||
std::vector<QueryFuncRef> GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) {
|
||||
std::vector<QueryFuncRef> callers;
|
||||
|
||||
optional<QueryFuncId> func_id = root.def.base;
|
||||
while (func_id) {
|
||||
optional<QueryFunc>& func = db->funcs[func_id->id];
|
||||
if (!func)
|
||||
break;
|
||||
|
||||
AddRange(&callers, func->callers);
|
||||
func_id = func->def.base;
|
||||
}
|
||||
|
||||
return callers;
|
||||
}
|
||||
|
||||
std::vector<QueryFuncRef> GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) {
|
||||
std::vector<QueryFuncRef> callers;
|
||||
|
||||
std::queue<QueryFuncId> queue;
|
||||
PushRange(&queue, root.derived);
|
||||
|
||||
while (!queue.empty()) {
|
||||
optional<QueryFunc>& func = db->funcs[queue.front().id];
|
||||
queue.pop();
|
||||
if (!func)
|
||||
continue;
|
||||
|
||||
PushRange(&queue, func->derived);
|
||||
AddRange(&callers, func->callers);
|
||||
}
|
||||
|
||||
return callers;
|
||||
}
|
||||
|
||||
optional<lsPosition> GetLsPosition(WorkingFile* working_file, const Position& position) {
|
||||
if (!working_file)
|
||||
return lsPosition(position.line - 1, position.column - 1);
|
||||
|
||||
optional<int> start = working_file->GetBufferLineFromIndexLine(position.line);
|
||||
if (!start)
|
||||
return nullopt;
|
||||
|
||||
return lsPosition(*start - 1, position.column - 1);
|
||||
}
|
||||
|
||||
optional<lsRange> GetLsRange(WorkingFile* working_file, const Range& location) {
|
||||
if (!working_file) {
|
||||
return lsRange(
|
||||
lsPosition(location.start.line - 1, location.start.column - 1),
|
||||
lsPosition(location.end.line - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
optional<int> start = working_file->GetBufferLineFromIndexLine(location.start.line);
|
||||
optional<int> end = working_file->GetBufferLineFromIndexLine(location.end.line);
|
||||
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);
|
||||
|
||||
return lsRange(
|
||||
lsPosition(*start - 1, location.start.column - 1),
|
||||
lsPosition(*end - 1, location.end.column - 1));
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path) {
|
||||
optional<QueryFile>& file = db->files[file_id.id];
|
||||
if (file) {
|
||||
*path = file->def.path;
|
||||
return lsDocumentUri::FromPath(*path);
|
||||
}
|
||||
else {
|
||||
*path = "";
|
||||
return lsDocumentUri::FromPath("");
|
||||
}
|
||||
}
|
||||
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) {
|
||||
optional<QueryFile>& file = db->files[file_id.id];
|
||||
if (file) {
|
||||
return lsDocumentUri::FromPath(file->def.path);
|
||||
}
|
||||
else {
|
||||
return lsDocumentUri::FromPath("");
|
||||
}
|
||||
}
|
||||
|
||||
optional<lsLocation> GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location) {
|
||||
std::string path;
|
||||
lsDocumentUri uri = GetLsDocumentUri(db, location.path, &path);
|
||||
optional<lsRange> range = GetLsRange(working_files->GetFileByFilename(path), location.range);
|
||||
if (!range)
|
||||
return nullopt;
|
||||
return lsLocation(uri, *range);
|
||||
}
|
||||
|
||||
NonElidedVector<lsLocation> GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations) {
|
||||
std::unordered_set<lsLocation> unique_locations;
|
||||
for (const QueryLocation& query_location : locations) {
|
||||
optional<lsLocation> location = GetLsLocation(db, working_files, query_location);
|
||||
if (!location)
|
||||
continue;
|
||||
unique_locations.insert(*location);
|
||||
}
|
||||
|
||||
NonElidedVector<lsLocation> result;
|
||||
result.reserve(unique_locations.size());
|
||||
result.assign(unique_locations.begin(), unique_locations.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a symbol. The symbol will have *NOT* have a location assigned.
|
||||
optional<lsSymbolInformation> GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol) {
|
||||
switch (symbol.kind) {
|
||||
case SymbolKind::File: {
|
||||
optional<QueryFile>& file = db->files[symbol.idx];
|
||||
if (!file)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = file->def.path;
|
||||
info.kind = lsSymbolKind::File;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Type: {
|
||||
optional<QueryType>& type = db->types[symbol.idx];
|
||||
if (!type)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = type->def.short_name;
|
||||
if (type->def.detailed_name != type->def.short_name)
|
||||
info.containerName = type->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Class;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Func: {
|
||||
optional<QueryFunc>& func = db->funcs[symbol.idx];
|
||||
if (!func)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name = func->def.short_name;
|
||||
info.containerName = func->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Function;
|
||||
|
||||
if (func->def.declaring_type.has_value()) {
|
||||
optional<QueryType>& container = db->types[func->def.declaring_type->id];
|
||||
if (container)
|
||||
info.kind = lsSymbolKind::Method;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Var: {
|
||||
optional<QueryVar>& var = db->vars[symbol.idx];
|
||||
if (!var)
|
||||
return nullopt;
|
||||
|
||||
lsSymbolInformation info;
|
||||
info.name += var->def.short_name;
|
||||
info.containerName = var->def.detailed_name;
|
||||
info.kind = lsSymbolKind::Variable;
|
||||
return info;
|
||||
}
|
||||
case SymbolKind::Invalid: {
|
||||
return nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
void AddCodeLens(
|
||||
const char* singular,
|
||||
const char* plural,
|
||||
CommonCodeLensParams* common,
|
||||
QueryLocation loc,
|
||||
const std::vector<QueryLocation>& uses,
|
||||
optional<QueryLocation> excluded,
|
||||
bool force_display) {
|
||||
TCodeLens code_lens;
|
||||
optional<lsRange> range = GetLsRange(common->working_file, loc.range);
|
||||
if (!range)
|
||||
return;
|
||||
code_lens.range = *range;
|
||||
code_lens.command = lsCommand<lsCodeLensCommandArguments>();
|
||||
code_lens.command->command = "cquery.showReferences";
|
||||
code_lens.command->arguments.uri = GetLsDocumentUri(common->db, loc.path);
|
||||
code_lens.command->arguments.position = code_lens.range.start;
|
||||
|
||||
// Add unique uses.
|
||||
std::unordered_set<lsLocation> unique_uses;
|
||||
for (const QueryLocation& use : uses) {
|
||||
if (excluded == use)
|
||||
continue;
|
||||
optional<lsLocation> location = GetLsLocation(common->db, common->working_files, use);
|
||||
if (!location)
|
||||
continue;
|
||||
unique_uses.insert(*location);
|
||||
}
|
||||
code_lens.command->arguments.locations.assign(unique_uses.begin(),
|
||||
unique_uses.end());
|
||||
|
||||
// User visible label
|
||||
size_t num_usages = unique_uses.size();
|
||||
code_lens.command->title = std::to_string(num_usages) + " ";
|
||||
if (num_usages == 1)
|
||||
code_lens.command->title += singular;
|
||||
else
|
||||
code_lens.command->title += plural;
|
||||
|
||||
if (force_display || unique_uses.size() > 0)
|
||||
common->result->push_back(code_lens);
|
||||
}
|
||||
|
||||
lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations, const std::string& new_text) {
|
||||
std::unordered_map<QueryFileId, lsTextDocumentEdit> path_to_edit;
|
||||
|
||||
for (auto& location : locations) {
|
||||
optional<lsLocation> ls_location = GetLsLocation(db, working_files, location);
|
||||
if (!ls_location)
|
||||
continue;
|
||||
|
||||
if (path_to_edit.find(location.path) == path_to_edit.end()) {
|
||||
path_to_edit[location.path] = lsTextDocumentEdit();
|
||||
|
||||
optional<QueryFile>& file = db->files[location.path.id];
|
||||
if (!file)
|
||||
continue;
|
||||
|
||||
const std::string& path = file->def.path;
|
||||
path_to_edit[location.path].textDocument.uri = lsDocumentUri::FromPath(path);
|
||||
|
||||
WorkingFile* working_file = working_files->GetFileByFilename(path);
|
||||
if (working_file)
|
||||
path_to_edit[location.path].textDocument.version = working_file->version;
|
||||
}
|
||||
|
||||
lsTextEdit edit;
|
||||
edit.range = ls_location->range;
|
||||
edit.newText = new_text;
|
||||
|
||||
// vscode complains if we submit overlapping text edits.
|
||||
auto& edits = path_to_edit[location.path].edits;
|
||||
if (std::find(edits.begin(), edits.end(), edit) == edits.end())
|
||||
edits.push_back(edit);
|
||||
}
|
||||
|
||||
|
||||
lsWorkspaceEdit edit;
|
||||
for (const auto& changes : path_to_edit)
|
||||
edit.documentChanges.push_back(changes.second);
|
||||
return edit;
|
||||
}
|
||||
|
||||
std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) {
|
||||
std::vector<SymbolRef> symbols;
|
||||
symbols.reserve(1);
|
||||
|
||||
int target_line = position.line + 1;
|
||||
int target_column = position.character + 1;
|
||||
if (working_file) {
|
||||
optional<int> index_line = working_file->GetIndexLineFromBufferLine(target_line);
|
||||
if (index_line)
|
||||
target_line = *index_line;
|
||||
}
|
||||
|
||||
for (const SymbolRef& ref : file->def.all_symbols) {
|
||||
if (ref.loc.range.Contains(target_line, target_column))
|
||||
symbols.push_back(ref);
|
||||
}
|
||||
|
||||
// Order function symbols first. This makes goto definition work better when
|
||||
// used on a constructor.
|
||||
std::sort(symbols.begin(), symbols.end(), [](const SymbolRef& a, const SymbolRef& b) {
|
||||
if (a.idx.kind != b.idx.kind && a.idx.kind == SymbolKind::Func)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
return symbols;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryTypeHierarchyTree::TypeEntry> BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root) {
|
||||
optional<QueryType>& root_type = db->types[root.id];
|
||||
if (!root_type)
|
||||
return {};
|
||||
|
||||
NonElidedVector<Out_CqueryTypeHierarchyTree::TypeEntry> parent_entries;
|
||||
parent_entries.reserve(root_type->def.parents.size());
|
||||
|
||||
for (QueryTypeId parent_id : root_type->def.parents) {
|
||||
optional<QueryType>& parent_type = db->types[parent_id.id];
|
||||
if (!parent_type)
|
||||
continue;
|
||||
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry parent_entry;
|
||||
parent_entry.name = parent_type->def.detailed_name;
|
||||
if (parent_type->def.definition_spelling)
|
||||
parent_entry.location = GetLsLocation(db, working_files, *parent_type->def.definition_spelling);
|
||||
parent_entry.children = BuildParentTypeHierarchy(db, working_files, parent_id);
|
||||
|
||||
parent_entries.push_back(parent_entry);
|
||||
}
|
||||
|
||||
return parent_entries;
|
||||
}
|
||||
|
||||
|
||||
optional<Out_CqueryTypeHierarchyTree::TypeEntry> BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id) {
|
||||
optional<QueryType>& root_type = db->types[root_id.id];
|
||||
if (!root_type)
|
||||
return nullopt;
|
||||
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry entry;
|
||||
|
||||
// Name and location.
|
||||
entry.name = root_type->def.detailed_name;
|
||||
if (root_type->def.definition_spelling)
|
||||
entry.location = GetLsLocation(db, working_files, *root_type->def.definition_spelling);
|
||||
|
||||
entry.children.reserve(root_type->derived.size());
|
||||
|
||||
// Base types.
|
||||
Out_CqueryTypeHierarchyTree::TypeEntry base;
|
||||
base.name = "[[Base]]";
|
||||
base.location = entry.location;
|
||||
base.children = BuildParentTypeHierarchy(db, working_files, root_id);
|
||||
if (!base.children.empty())
|
||||
entry.children.push_back(base);
|
||||
|
||||
// Add derived.
|
||||
for (QueryTypeId derived : root_type->derived) {
|
||||
auto derived_entry = BuildTypeHierarchy(db, working_files, derived);
|
||||
if (derived_entry)
|
||||
entry.children.push_back(*derived_entry);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) {
|
||||
optional<QueryFunc>& root_func = db->funcs[root.id];
|
||||
if (!root_func)
|
||||
return {};
|
||||
if (!root_func->def.definition_spelling)
|
||||
return {};
|
||||
optional<lsLocation> def_loc = GetLsLocation(db, working_files, *root_func->def.definition_spelling);
|
||||
if (!def_loc)
|
||||
return {};
|
||||
|
||||
Out_CqueryCallTree::CallEntry entry;
|
||||
entry.name = root_func->def.short_name;
|
||||
entry.usr = root_func->def.usr;
|
||||
entry.location = *def_loc;
|
||||
entry.hasCallers = !root_func->callers.empty();
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> result;
|
||||
result.push_back(entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) {
|
||||
optional<QueryFunc>& root_func = db->funcs[root.id];
|
||||
if (!root_func)
|
||||
return {};
|
||||
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> result;
|
||||
result.reserve(root_func->callers.size());
|
||||
for (QueryFuncRef caller : root_func->callers) {
|
||||
optional<lsLocation> call_location = GetLsLocation(db, working_files, caller.loc);
|
||||
if (!call_location)
|
||||
continue;
|
||||
|
||||
if (caller.has_id()) {
|
||||
optional<QueryFunc>& call_func = db->funcs[caller.id_.id];
|
||||
if (!call_func)
|
||||
continue;
|
||||
|
||||
Out_CqueryCallTree::CallEntry call_entry;
|
||||
call_entry.name = call_func->def.short_name;
|
||||
call_entry.usr = call_func->def.usr;
|
||||
call_entry.location = *call_location;
|
||||
call_entry.hasCallers = !call_func->callers.empty();
|
||||
result.push_back(call_entry);
|
||||
}
|
||||
else {
|
||||
// TODO: See if we can do a better job here. Need more information from
|
||||
// the indexer.
|
||||
Out_CqueryCallTree::CallEntry call_entry;
|
||||
call_entry.name = "Likely Constructor";
|
||||
call_entry.usr = "no_usr";
|
||||
call_entry.location = *call_location;
|
||||
call_entry.hasCallers = false;
|
||||
result.push_back(call_entry);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
57
src/query_utils.h
Normal file
57
src/query_utils.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "query_utils.h"
|
||||
|
||||
#include "query.h"
|
||||
#include "working_files.h"
|
||||
|
||||
#include <optional.h>
|
||||
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id);
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id);
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id);
|
||||
optional<QueryLocation> GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
optional<QueryLocation> GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
std::string GetHoverForSymbol(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
optional<QueryFileId> GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncRef>& refs);
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryTypeId>& ids);
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryFuncId>& ids);
|
||||
std::vector<QueryLocation> ToQueryLocation(QueryDatabase* db, const std::vector<QueryVarId>& ids);
|
||||
std::vector<QueryLocation> GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
std::vector<QueryLocation> GetDeclarationsOfSymbolForGotoDefinition(QueryDatabase* db, const SymbolIdx& symbol);
|
||||
optional<QueryLocation> GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func);
|
||||
std::vector<QueryFuncRef> GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root);
|
||||
std::vector<QueryFuncRef> GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root);
|
||||
optional<lsPosition> GetLsPosition(WorkingFile* working_file, const Position& position);
|
||||
optional<lsRange> GetLsRange(WorkingFile* working_file, const Range& location);
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id, std::string* path);
|
||||
lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id);
|
||||
optional<lsLocation> GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location);
|
||||
NonElidedVector<lsLocation> GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations);
|
||||
// Returns a symbol. The symbol will have *NOT* have a location assigned.
|
||||
optional<lsSymbolInformation> GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol);
|
||||
|
||||
struct CommonCodeLensParams {
|
||||
std::vector<TCodeLens>* result;
|
||||
QueryDatabase* db;
|
||||
WorkingFiles* working_files;
|
||||
WorkingFile* working_file;
|
||||
};
|
||||
|
||||
void AddCodeLens(
|
||||
const char* singular,
|
||||
const char* plural,
|
||||
CommonCodeLensParams* common,
|
||||
QueryLocation loc,
|
||||
const std::vector<QueryLocation>& uses,
|
||||
optional<QueryLocation> excluded,
|
||||
bool force_display);
|
||||
|
||||
lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_files, const std::vector<QueryLocation>& locations, const std::string& new_text);
|
||||
|
||||
std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position);
|
||||
NonElidedVector<Out_CqueryTypeHierarchyTree::TypeEntry> BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root);
|
||||
optional<Out_CqueryTypeHierarchyTree::TypeEntry> BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id);
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root);
|
||||
NonElidedVector<Out_CqueryCallTree::CallEntry> BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root);
|
@ -1,5 +1,6 @@
|
||||
#include "working_files.h"
|
||||
|
||||
#include "lex_utils.h"
|
||||
#include "position.h"
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
@ -30,39 +31,6 @@ lsPosition GetPositionForOffset(const std::string& content, int offset) {
|
||||
|
||||
} // namespace
|
||||
|
||||
int GetOffsetForPosition(lsPosition position, const std::string& content) {
|
||||
int offset = 0;
|
||||
|
||||
int remaining_lines = position.line;
|
||||
while (remaining_lines > 0) {
|
||||
if (content[offset] == '\n')
|
||||
--remaining_lines;
|
||||
++offset;
|
||||
}
|
||||
|
||||
return offset + position.character;
|
||||
}
|
||||
|
||||
lsPosition CharPos(const std::string& search, char character, int character_offset) {
|
||||
lsPosition result;
|
||||
int index = 0;
|
||||
while (index < search.size()) {
|
||||
char c = search[index];
|
||||
if (c == character)
|
||||
break;
|
||||
if (c == '\n') {
|
||||
result.line += 1;
|
||||
result.character = 0;
|
||||
}
|
||||
else {
|
||||
result.character += 1;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
assert(index < search.size());
|
||||
result.character += character_offset;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
WorkingFile::WorkingFile(const std::string& filename, const std::string& buffer_content)
|
||||
|
@ -12,11 +12,6 @@
|
||||
using std::experimental::optional;
|
||||
using std::experimental::nullopt;
|
||||
|
||||
// Utility method to map |position| to an offset inside of |content|.
|
||||
int GetOffsetForPosition(lsPosition position, const std::string& content);
|
||||
// Utility method to find a position for the given character.
|
||||
lsPosition CharPos(const std::string& search, char character, int character_offset = 0);
|
||||
|
||||
struct WorkingFile {
|
||||
int version = 0;
|
||||
std::string filename;
|
||||
@ -52,6 +47,8 @@ struct WorkingFile {
|
||||
|
||||
optional<std::string> GetBufferLineContentFromIndexLine(int indexed_line, optional<int>* out_buffer_line) const;
|
||||
|
||||
// TODO: Move FindClosestCallNameInBuffer and FindStableCompletionSource into lex_utils.h/cc
|
||||
|
||||
// Finds the closest 'callable' name prior to position. This is used for
|
||||
// signature help to filter code completion results.
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user