diff --git a/src/command_line.cc b/src/command_line.cc index 4b70554e..d41ee5ad 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -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 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 GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id) { - optional& type = db->types[id.id]; - if (type) - return type->def.definition_spelling; - return nullopt; -} - -optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id) { - optional& func = db->funcs[id.id]; - if (func) - return func->def.definition_spelling; - return nullopt; -} - -optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id) { - optional& var = db->vars[id.id]; - if (var) - return var->def.definition_spelling; - return nullopt; -} - -optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { - switch (symbol.kind) { - case SymbolKind::Type: { - optional& type = db->types[symbol.idx]; - if (type) - return type->def.definition_spelling; - break; - } - case SymbolKind::Func: { - optional& func = db->funcs[symbol.idx]; - if (func) - return func->def.definition_spelling; - break; - } - case SymbolKind::Var: { - optional& 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 GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { - switch (symbol.kind) { - case SymbolKind::Type: { - optional& type = db->types[symbol.idx]; - if (type) - return type->def.definition_extent; - break; - } - case SymbolKind::Func: { - optional& func = db->funcs[symbol.idx]; - if (func) - return func->def.definition_extent; - break; - } - case SymbolKind::Var: { - optional& 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& type = db->types[symbol.idx]; - if (type) - return type->def.detailed_name; - break; - } - case SymbolKind::Func: { - optional& func = db->funcs[symbol.idx]; - if (func) - return func->def.detailed_name; - break; - } - case SymbolKind::Var: { - optional& 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 GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol) { - switch (symbol.kind) { - case SymbolKind::Type: { - optional& type = db->types[symbol.idx]; - if (type && type->def.definition_spelling) - return type->def.definition_spelling->path; - break; - } - case SymbolKind::Func: { - optional& 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& 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 ToQueryLocation(QueryDatabase* db, const std::vector& refs) { - std::vector locs; - locs.reserve(refs.size()); - for (const QueryFuncRef& ref : refs) - locs.push_back(ref.loc); - return locs; -} -std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { - std::vector locs; - locs.reserve(ids.size()); - for (const QueryTypeId& id : ids) { - optional loc = GetDefinitionSpellingOfSymbol(db, id); - if (loc) - locs.push_back(loc.value()); - } - return locs; -} -std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { - std::vector locs; - locs.reserve(ids.size()); - for (const QueryFuncId& id : ids) { - optional loc = GetDefinitionSpellingOfSymbol(db, id); - if (loc) - locs.push_back(loc.value()); - } - return locs; -} -std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { - std::vector locs; - locs.reserve(ids.size()); - for (const QueryVarId& id : ids) { - optional loc = GetDefinitionSpellingOfSymbol(db, id); - if (loc) - locs.push_back(loc.value()); - } - return locs; -} - - - -std::vector GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { - switch (symbol.kind) { - case SymbolKind::Type: { - optional& type = db->types[symbol.idx]; - if (type) - return type->uses; - break; - } - case SymbolKind::Func: { - // TODO: the vector allocation could be avoided. - optional& func = db->funcs[symbol.idx]; - if (func) { - std::vector 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& var = db->vars[symbol.idx]; - if (var) - return var->uses; - break; - } - case SymbolKind::File: - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - } - return {}; -} - -std::vector 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& type = db->types[symbol.idx]; - if (type) { - optional declaration = type->def.definition_spelling; - if (declaration) - return { *declaration }; - } - break; - } - case SymbolKind::Func: { - optional& func = db->funcs[symbol.idx]; - if (func) - return func->declarations; - break; - } - case SymbolKind::Var: { - optional& var = db->vars[symbol.idx]; - if (var) { - optional declaration = var->def.declaration; - if (declaration) - return { *declaration }; - } - break; - } - default: - break; - } - - return {}; -} - -optional GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func) { - if (!func.def.base) - return nullopt; - optional& 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 GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) { - std::vector callers; - - optional func_id = root.def.base; - while (func_id) { - optional& func = db->funcs[func_id->id]; - if (!func) - break; - - AddRange(&callers, func->callers); - func_id = func->def.base; - } - - return callers; -} - -std::vector GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) { - std::vector callers; - - std::queue queue; - PushRange(&queue, root.derived); - - while (!queue.empty()) { - optional& func = db->funcs[queue.front().id]; - queue.pop(); - if (!func) - continue; - - PushRange(&queue, func->derived); - AddRange(&callers, func->callers); - } - - return callers; -} - -optional GetLsPosition(WorkingFile* working_file, const Position& position) { - if (!working_file) - return lsPosition(position.line - 1, position.column - 1); - - optional start = working_file->GetBufferLineFromIndexLine(position.line); - if (!start) - return nullopt; - - return lsPosition(*start - 1, position.column - 1); -} - -optional 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 start = working_file->GetBufferLineFromIndexLine(location.start.line); - optional 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& 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& file = db->files[file_id.id]; - if (file) { - return lsDocumentUri::FromPath(file->def.path); - } - else { - return lsDocumentUri::FromPath(""); - } -} - -optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location) { - std::string path; - lsDocumentUri uri = GetLsDocumentUri(db, location.path, &path); - optional range = GetLsRange(working_files->GetFileByFilename(path), location.range); - if (!range) - return nullopt; - return lsLocation(uri, *range); -} - -NonElidedVector GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector& locations) { - std::unordered_set unique_locations; - for (const QueryLocation& query_location : locations) { - optional location = GetLsLocation(db, working_files, query_location); - if (!location) - continue; - unique_locations.insert(*location); - } - - NonElidedVector 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 GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol) { - switch (symbol.kind) { - case SymbolKind::File: { - optional& 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& 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& 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& container = db->types[func->def.declaring_type->id]; - if (container) - info.kind = lsSymbolKind::Method; - } - - return info; - } - case SymbolKind::Var: { - optional& 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* result; - QueryDatabase* db; - WorkingFiles* working_files; - WorkingFile* working_file; -}; - -void AddCodeLens( - const char* singular, - const char* plural, - CommonCodeLensParams* common, - QueryLocation loc, - const std::vector& uses, - optional excluded, - bool force_display) { - TCodeLens code_lens; - optional range = GetLsRange(common->working_file, loc.range); - if (!range) - return; - code_lens.range = *range; - code_lens.command = lsCommand(); - 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 unique_uses; - for (const QueryLocation& use : uses) { - if (excluded == use) - continue; - optional 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& locations, const std::string& new_text) { - std::unordered_map path_to_edit; - - for (auto& location : locations) { - optional 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& 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 FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) { - std::vector symbols; - symbols.reserve(1); - - int target_line = position.line + 1; - int target_column = position.character + 1; - if (working_file) { - optional 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 BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root) { - optional& root_type = db->types[root.id]; - if (!root_type) - return {}; - - NonElidedVector parent_entries; - parent_entries.reserve(root_type->def.parents.size()); - - for (QueryTypeId parent_id : root_type->def.parents) { - optional& 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 BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id) { - optional& 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 BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { - optional& root_func = db->funcs[root.id]; - if (!root_func) - return {}; - if (!root_func->def.definition_spelling) - return {}; - optional 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 result; - result.push_back(entry); - return result; -} - -NonElidedVector BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { - optional& root_func = db->funcs[root.id]; - if (!root_func) - return {}; - - NonElidedVector result; - result.reserve(root_func->callers.size()); - for (QueryFuncRef caller : root_func->callers) { - optional call_location = GetLsLocation(db, working_files, caller.loc); - if (!call_location) - continue; - - if (caller.has_id()) { - optional& 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& inactive) { Out_CquerySetInactiveRegion out; @@ -923,112 +207,6 @@ void PublishInactiveLines(WorkingFile* working_file, const std::vector& i } -void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional 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 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 FindIncludeLine(const std::vector& lines, const std::string& full_include_line) { // diff --git a/src/lex_utils.cc b/src/lex_utils.cc new file mode 100644 index 00000000..a53cf3cf --- /dev/null +++ b/src/lex_utils.cc @@ -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 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 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 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); +} \ No newline at end of file diff --git a/src/lex_utils.h b/src/lex_utils.h new file mode 100644 index 00000000..76e005ab --- /dev/null +++ b/src/lex_utils.h @@ -0,0 +1,20 @@ +#pragma once + +#include "language_server_api.h" + +#include + + +// 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 ExtractQuotedRange(int line_number, const std::string& line); + +void LexFunctionDeclaration(const std::string& buffer_content, lsPosition declaration_spelling, optional type_name, std::string* insert_text, int* newlines_after_name); + +std::string LexWordAroundPos(lsPosition position, const std::string& content); \ No newline at end of file diff --git a/src/query_utils.cc b/src/query_utils.cc new file mode 100644 index 00000000..4ae0d9f7 --- /dev/null +++ b/src/query_utils.cc @@ -0,0 +1,679 @@ +#include "query_utils.h" + +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id) { + optional& type = db->types[id.id]; + if (type) + return type->def.definition_spelling; + return nullopt; +} + +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id) { + optional& func = db->funcs[id.id]; + if (func) + return func->def.definition_spelling; + return nullopt; +} + +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id) { + optional& var = db->vars[id.id]; + if (var) + return var->def.definition_spelling; + return nullopt; +} + +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { + switch (symbol.kind) { + case SymbolKind::Type: { + optional& type = db->types[symbol.idx]; + if (type) + return type->def.definition_spelling; + break; + } + case SymbolKind::Func: { + optional& func = db->funcs[symbol.idx]; + if (func) + return func->def.definition_spelling; + break; + } + case SymbolKind::Var: { + optional& 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 GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { + switch (symbol.kind) { + case SymbolKind::Type: { + optional& type = db->types[symbol.idx]; + if (type) + return type->def.definition_extent; + break; + } + case SymbolKind::Func: { + optional& func = db->funcs[symbol.idx]; + if (func) + return func->def.definition_extent; + break; + } + case SymbolKind::Var: { + optional& 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& type = db->types[symbol.idx]; + if (type) + return type->def.detailed_name; + break; + } + case SymbolKind::Func: { + optional& func = db->funcs[symbol.idx]; + if (func) + return func->def.detailed_name; + break; + } + case SymbolKind::Var: { + optional& 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 GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol) { + switch (symbol.kind) { + case SymbolKind::Type: { + optional& type = db->types[symbol.idx]; + if (type && type->def.definition_spelling) + return type->def.definition_spelling->path; + break; + } + case SymbolKind::Func: { + optional& 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& 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 ToQueryLocation(QueryDatabase* db, const std::vector& refs) { + std::vector locs; + locs.reserve(refs.size()); + for (const QueryFuncRef& ref : refs) + locs.push_back(ref.loc); + return locs; +} +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { + std::vector locs; + locs.reserve(ids.size()); + for (const QueryTypeId& id : ids) { + optional loc = GetDefinitionSpellingOfSymbol(db, id); + if (loc) + locs.push_back(loc.value()); + } + return locs; +} +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { + std::vector locs; + locs.reserve(ids.size()); + for (const QueryFuncId& id : ids) { + optional loc = GetDefinitionSpellingOfSymbol(db, id); + if (loc) + locs.push_back(loc.value()); + } + return locs; +} +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids) { + std::vector locs; + locs.reserve(ids.size()); + for (const QueryVarId& id : ids) { + optional loc = GetDefinitionSpellingOfSymbol(db, id); + if (loc) + locs.push_back(loc.value()); + } + return locs; +} + + + +std::vector GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol) { + switch (symbol.kind) { + case SymbolKind::Type: { + optional& type = db->types[symbol.idx]; + if (type) + return type->uses; + break; + } + case SymbolKind::Func: { + // TODO: the vector allocation could be avoided. + optional& func = db->funcs[symbol.idx]; + if (func) { + std::vector 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& var = db->vars[symbol.idx]; + if (var) + return var->uses; + break; + } + case SymbolKind::File: + case SymbolKind::Invalid: { + assert(false && "unexpected"); + break; + } + } + return {}; +} + +std::vector 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& type = db->types[symbol.idx]; + if (type) { + optional declaration = type->def.definition_spelling; + if (declaration) + return { *declaration }; + } + break; + } + case SymbolKind::Func: { + optional& func = db->funcs[symbol.idx]; + if (func) + return func->declarations; + break; + } + case SymbolKind::Var: { + optional& var = db->vars[symbol.idx]; + if (var) { + optional declaration = var->def.declaration; + if (declaration) + return { *declaration }; + } + break; + } + default: + break; + } + + return {}; +} + +optional GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func) { + if (!func.def.base) + return nullopt; + optional& 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 GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root) { + std::vector callers; + + optional func_id = root.def.base; + while (func_id) { + optional& func = db->funcs[func_id->id]; + if (!func) + break; + + AddRange(&callers, func->callers); + func_id = func->def.base; + } + + return callers; +} + +std::vector GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root) { + std::vector callers; + + std::queue queue; + PushRange(&queue, root.derived); + + while (!queue.empty()) { + optional& func = db->funcs[queue.front().id]; + queue.pop(); + if (!func) + continue; + + PushRange(&queue, func->derived); + AddRange(&callers, func->callers); + } + + return callers; +} + +optional GetLsPosition(WorkingFile* working_file, const Position& position) { + if (!working_file) + return lsPosition(position.line - 1, position.column - 1); + + optional start = working_file->GetBufferLineFromIndexLine(position.line); + if (!start) + return nullopt; + + return lsPosition(*start - 1, position.column - 1); +} + +optional 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 start = working_file->GetBufferLineFromIndexLine(location.start.line); + optional 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& 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& file = db->files[file_id.id]; + if (file) { + return lsDocumentUri::FromPath(file->def.path); + } + else { + return lsDocumentUri::FromPath(""); + } +} + +optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location) { + std::string path; + lsDocumentUri uri = GetLsDocumentUri(db, location.path, &path); + optional range = GetLsRange(working_files->GetFileByFilename(path), location.range); + if (!range) + return nullopt; + return lsLocation(uri, *range); +} + +NonElidedVector GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector& locations) { + std::unordered_set unique_locations; + for (const QueryLocation& query_location : locations) { + optional location = GetLsLocation(db, working_files, query_location); + if (!location) + continue; + unique_locations.insert(*location); + } + + NonElidedVector 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 GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol) { + switch (symbol.kind) { + case SymbolKind::File: { + optional& 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& 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& 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& container = db->types[func->def.declaring_type->id]; + if (container) + info.kind = lsSymbolKind::Method; + } + + return info; + } + case SymbolKind::Var: { + optional& 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& uses, + optional excluded, + bool force_display) { + TCodeLens code_lens; + optional range = GetLsRange(common->working_file, loc.range); + if (!range) + return; + code_lens.range = *range; + code_lens.command = lsCommand(); + 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 unique_uses; + for (const QueryLocation& use : uses) { + if (excluded == use) + continue; + optional 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& locations, const std::string& new_text) { + std::unordered_map path_to_edit; + + for (auto& location : locations) { + optional 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& 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 FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position) { + std::vector symbols; + symbols.reserve(1); + + int target_line = position.line + 1; + int target_column = position.character + 1; + if (working_file) { + optional 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 BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root) { + optional& root_type = db->types[root.id]; + if (!root_type) + return {}; + + NonElidedVector parent_entries; + parent_entries.reserve(root_type->def.parents.size()); + + for (QueryTypeId parent_id : root_type->def.parents) { + optional& 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 BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id) { + optional& 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 BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { + optional& root_func = db->funcs[root.id]; + if (!root_func) + return {}; + if (!root_func->def.definition_spelling) + return {}; + optional 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 result; + result.push_back(entry); + return result; +} + +NonElidedVector BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root) { + optional& root_func = db->funcs[root.id]; + if (!root_func) + return {}; + + NonElidedVector result; + result.reserve(root_func->callers.size()); + for (QueryFuncRef caller : root_func->callers) { + optional call_location = GetLsLocation(db, working_files, caller.loc); + if (!call_location) + continue; + + if (caller.has_id()) { + optional& 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; +} \ No newline at end of file diff --git a/src/query_utils.h b/src/query_utils.h new file mode 100644 index 00000000..124bcffb --- /dev/null +++ b/src/query_utils.h @@ -0,0 +1,57 @@ +#pragma once + +#include "query_utils.h" + +#include "query.h" +#include "working_files.h" + +#include + +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryTypeId& id); +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryFuncId& id); +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const QueryVarId& id); +optional GetDefinitionSpellingOfSymbol(QueryDatabase* db, const SymbolIdx& symbol); +optional GetDefinitionExtentOfSymbol(QueryDatabase* db, const SymbolIdx& symbol); +std::string GetHoverForSymbol(QueryDatabase* db, const SymbolIdx& symbol); +optional GetDeclarationFileForSymbol(QueryDatabase* db, const SymbolIdx& symbol); +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& refs); +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids); +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids); +std::vector ToQueryLocation(QueryDatabase* db, const std::vector& ids); +std::vector GetUsesOfSymbol(QueryDatabase* db, const SymbolIdx& symbol); +std::vector GetDeclarationsOfSymbolForGotoDefinition(QueryDatabase* db, const SymbolIdx& symbol); +optional GetBaseDefinitionOrDeclarationSpelling(QueryDatabase* db, QueryFunc& func); +std::vector GetCallersForAllBaseFunctions(QueryDatabase* db, QueryFunc& root); +std::vector GetCallersForAllDerivedFunctions(QueryDatabase* db, QueryFunc& root); +optional GetLsPosition(WorkingFile* working_file, const Position& position); +optional 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 GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, const QueryLocation& location); +NonElidedVector GetLsLocations(QueryDatabase* db, WorkingFiles* working_files, const std::vector& locations); +// Returns a symbol. The symbol will have *NOT* have a location assigned. +optional GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx symbol); + +struct CommonCodeLensParams { + std::vector* result; + QueryDatabase* db; + WorkingFiles* working_files; + WorkingFile* working_file; +}; + +void AddCodeLens( + const char* singular, + const char* plural, + CommonCodeLensParams* common, + QueryLocation loc, + const std::vector& uses, + optional excluded, + bool force_display); + +lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_files, const std::vector& locations, const std::string& new_text); + +std::vector FindSymbolsAtLocation(WorkingFile* working_file, QueryFile* file, lsPosition position); +NonElidedVector BuildParentTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root); +optional BuildTypeHierarchy(QueryDatabase* db, WorkingFiles* working_files, QueryTypeId root_id); +NonElidedVector BuildInitialCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root); +NonElidedVector BuildExpandCallTree(QueryDatabase* db, WorkingFiles* working_files, QueryFuncId root); \ No newline at end of file diff --git a/src/working_files.cc b/src/working_files.cc index 88fb8b4b..29c34d6e 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -1,5 +1,6 @@ #include "working_files.h" +#include "lex_utils.h" #include "position.h" #include @@ -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) diff --git a/src/working_files.h b/src/working_files.h index 6054869e..bdea8456 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -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 GetBufferLineContentFromIndexLine(int indexed_line, optional* 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. //