Skip to content

Commit

Permalink
Merge pull request #80973 from ryanabx/pr/79988
Browse files Browse the repository at this point in the history
Language Server: Improve hovered symbol resolution, fix renaming bugs, implement reference lookup
  • Loading branch information
akien-mga committed Sep 12, 2023
2 parents 2c2ca3d + 0202a36 commit d507708
Show file tree
Hide file tree
Showing 18 changed files with 1,653 additions and 352 deletions.
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);
}
};

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

0 comments on commit d507708

Please sign in to comment.