Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Language Server: Improve hovered symbol resolution, fix renaming bugs, implement reference lookup #80973

Merged
merged 1 commit into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1459,8 +1459,13 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context,
if (p_expression->is_constant) {
// Already has a value, so just use that.
r_type = _type_from_variant(p_expression->reduced_value);
if (p_expression->get_datatype().kind == GDScriptParser::DataType::ENUM) {
r_type.type = p_expression->get_datatype();
switch (p_expression->get_datatype().kind) {
case GDScriptParser::DataType::ENUM:
case GDScriptParser::DataType::CLASS:
r_type.type = p_expression->get_datatype();
break;
default:
break;
}
found = true;
} else {
Expand Down
462 changes: 311 additions & 151 deletions modules/gdscript/language_server/gdscript_extend_parser.cpp

Large diffs are not rendered by default.

78 changes: 74 additions & 4 deletions modules/gdscript/language_server/gdscript_extend_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#ifndef LINE_NUMBER_TO_INDEX
#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1)
#endif
#ifndef COLUMN_NUMBER_TO_INDEX
#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1)
#endif

#ifndef SYMBOL_SEPERATOR
#define SYMBOL_SEPERATOR "::"
Expand All @@ -50,6 +53,64 @@

typedef HashMap<String, const lsp::DocumentSymbol *> ClassMembers;

/**
* Represents a Position as used by GDScript Parser. Used for conversion to and from `lsp::Position`.
*
* Difference to `lsp::Position`:
* * Line & Char/column: 1-based
* * LSP: both 0-based
* * Tabs are expanded to columns using tab size (`text_editor/behavior/indent/size`).
* * LSP: tab is single char
*
* Example:
* ```gdscript
* →→var my_value = 42
* ```
* `_` is at:
* * Godot: `column=12`
* * using `indent/size=4`
* * Note: counting starts at `1`
* * LSP: `character=8`
* * Note: counting starts at `0`
*/
struct GodotPosition {
int line;
int column;

GodotPosition(int p_line, int p_column) :
line(p_line), column(p_column) {}

lsp::Position to_lsp(const Vector<String> &p_lines) const;
static GodotPosition from_lsp(const lsp::Position p_pos, const Vector<String> &p_lines);

bool operator==(const GodotPosition &p_other) const {
return line == p_other.line && column == p_other.column;
}

String to_string() const {
return vformat("(%d,%d)", line, column);
}
};
ryanabx marked this conversation as resolved.
Show resolved Hide resolved

struct GodotRange {
GodotPosition start;
GodotPosition end;

GodotRange(GodotPosition p_start, GodotPosition p_end) :
start(p_start), end(p_end) {}

lsp::Range to_lsp(const Vector<String> &p_lines) const;
static GodotRange from_lsp(const lsp::Range &p_range, const Vector<String> &p_lines);

bool operator==(const GodotRange &p_other) const {
return start == p_other.start && end == p_other.end;
}

String to_string() const {
return vformat("[%s:%s]", start.to_string(), end.to_string());
}
};

class ExtendGDScriptParser : public GDScriptParser {
String path;
Vector<String> lines;
Expand All @@ -60,6 +121,8 @@ class ExtendGDScriptParser : public GDScriptParser {
ClassMembers members;
HashMap<String, ClassMembers> inner_classes;

lsp::Range range_of_node(const GDScriptParser::Node *p_node) const;

void update_diagnostics();

void update_symbols();
Expand All @@ -70,8 +133,7 @@ class ExtendGDScriptParser : public GDScriptParser {
Dictionary dump_function_api(const GDScriptParser::FunctionNode *p_func) const;
Dictionary dump_class_api(const GDScriptParser::ClassNode *p_class) const;

String parse_documentation(int p_line, bool p_docs_down = false);
const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent) const;
const lsp::DocumentSymbol *search_symbol_defined_at_line(int p_line, const lsp::DocumentSymbol &p_parent, const String &p_symbol_name = "") const;

Array member_completions;

Expand All @@ -87,10 +149,18 @@ class ExtendGDScriptParser : public GDScriptParser {

String get_text_for_completion(const lsp::Position &p_cursor) const;
String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_required = false) const;
String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
String get_identifier_under_position(const lsp::Position &p_position, lsp::Range &r_range) const;
String get_uri() const;

const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line) const;
/**
* `p_symbol_name` gets ignored if empty. Otherwise symbol must match passed in named.
*
* Necessary when multiple symbols at same line for example with `func`:
* `func handle_arg(arg: int):`
* -> Without `p_symbol_name`: returns `handle_arg`. Even if parameter (`arg`) is wanted.
* With `p_symbol_name`: symbol name MUST match `p_symbol_name`: returns `arg`.
*/
const lsp::DocumentSymbol *get_symbol_defined_at_line(int p_line, const String &p_symbol_name = "") const;
const lsp::DocumentSymbol *get_member_symbol(const String &p_name, const String &p_subclass = "") const;
const List<lsp::DocumentLink> &get_document_links() const;

Expand Down
10 changes: 10 additions & 0 deletions modules/gdscript/language_server/gdscript_language_protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ void GDScriptLanguageProtocol::stop() {
}

void GDScriptLanguageProtocol::notify_client(const String &p_method, const Variant &p_params, int p_client_id) {
#ifdef TESTS_ENABLED
if (clients.is_empty()) {
return;
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
Expand All @@ -294,6 +299,11 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia
}

void GDScriptLanguageProtocol::request_client(const String &p_method, const Variant &p_params, int p_client_id) {
#ifdef TESTS_ENABLED
if (clients.is_empty()) {
return;
}
#endif
if (p_client_id == -1) {
ERR_FAIL_COND_MSG(latest_client_id == -1,
"GDScript LSP: Can't notify client as none was connected.");
Expand Down
55 changes: 48 additions & 7 deletions modules/gdscript/language_server/gdscript_text_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("completion"), &GDScriptTextDocument::completion);
ClassDB::bind_method(D_METHOD("resolve"), &GDScriptTextDocument::resolve);
ClassDB::bind_method(D_METHOD("rename"), &GDScriptTextDocument::rename);
ClassDB::bind_method(D_METHOD("prepareRename"), &GDScriptTextDocument::prepareRename);
ClassDB::bind_method(D_METHOD("references"), &GDScriptTextDocument::references);
ClassDB::bind_method(D_METHOD("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
Expand Down Expand Up @@ -161,11 +163,8 @@ Array GDScriptTextDocument::documentSymbol(const Dictionary &p_params) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(uri);
Array arr;
if (HashMap<String, ExtendGDScriptParser *>::ConstIterator parser = GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts.find(path)) {
Vector<lsp::DocumentedSymbolInformation> list;
parser->value->get_symbols().symbol_tree_as_list(uri, list);
for (int i = 0; i < list.size(); i++) {
arr.push_back(list[i].to_json());
}
lsp::DocumentSymbol symbol = parser->value->get_symbols();
arr.push_back(symbol.to_json(true));
}
return arr;
}
Expand Down Expand Up @@ -253,6 +252,48 @@ Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) {
return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name);
}

Variant GDScriptTextDocument::prepareRename(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);

lsp::DocumentSymbol symbol;
lsp::Range range;
if (GDScriptLanguageProtocol::get_singleton()->get_workspace()->can_rename(params, symbol, range)) {
return Variant(range.to_json());
}

// `null` -> rename not valid at current location.
return Variant();
}

Array GDScriptTextDocument::references(const Dictionary &p_params) {
Array res;

lsp::ReferenceParams params;
params.load(p_params);

const lsp::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
if (symbol) {
Vector<lsp::Location> usages = GDScriptLanguageProtocol::get_singleton()->get_workspace()->find_all_usages(*symbol);
res.resize(usages.size());
int declaration_adjustment = 0;
for (int i = 0; i < usages.size(); i++) {
lsp::Location usage = usages[i];
if (!params.context.includeDeclaration && usage.range == symbol->range) {
declaration_adjustment++;
continue;
}
res[i - declaration_adjustment] = usages[i].to_json();
}

if (declaration_adjustment > 0) {
res.resize(res.size() - declaration_adjustment);
}
}

return res;
}

Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
lsp::CompletionItem item;
item.load(p_params);
Expand Down Expand Up @@ -450,7 +491,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
if (symbol) {
lsp::Location location;
location.uri = symbol->uri;
location.range = symbol->range;
location.range = symbol->selectionRange;
const String &path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(symbol->uri);
if (file_checker->file_exists(path)) {
arr.push_back(location.to_json());
Expand All @@ -464,7 +505,7 @@ Array GDScriptTextDocument::find_symbols(const lsp::TextDocumentPositionParams &
if (!s->uri.is_empty()) {
lsp::Location location;
location.uri = s->uri;
location.range = s->range;
location.range = s->selectionRange;
arr.push_back(location.to_json());
r_list.push_back(s);
}
Expand Down
2 changes: 2 additions & 0 deletions modules/gdscript/language_server/gdscript_text_document.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class GDScriptTextDocument : public RefCounted {
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
Variant prepareRename(const Dictionary &p_params);
Array references(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
Array documentLink(const Dictionary &p_params);
Expand Down
Loading