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

Implement LSP didSave notification and rename request #48615

Merged
merged 1 commit into from
Aug 4, 2021
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
1 change: 1 addition & 0 deletions core/object/script_language.h
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ class ScriptLanguage {
Ref<Script> script;
String class_name;
String class_member;
String class_path;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intended that the master branch has this addition but the matching 3.x PR didn't seem to need it? #48616

That's the only reason I hadn't merged this yet as I was waiting for @vnen to check it since changing a core struct that affects all language bindings increases the scope of this PR significantly. On the other hand I see that LookupResult only seems to be used by the GDScript editor so it's probably fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the fundamentally there were different values between GDScript 2.0 and GDScript 1.0 that made accessing the base class for an extending class impossible in Godot 4.0, so I needed a way to reach it at the end of the day.

I made sure to only add a value, and changed nothing else about the code, so that it won't break any bindings that don't need it.

int location;
};

Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2799,6 +2799,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
if (base_type.class_type->has_member(p_symbol)) {
r_result.type = ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION;
r_result.location = base_type.class_type->get_member(p_symbol).get_line();
r_result.class_path = base_type.script_path;
return OK;
}
base_type = base_type.class_type->base_type;
Expand Down
28 changes: 28 additions & 0 deletions modules/gdscript/language_server/gdscript_text_document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ void GDScriptTextDocument::_bind_methods() {
ClassDB::bind_method(D_METHOD("didOpen"), &GDScriptTextDocument::didOpen);
ClassDB::bind_method(D_METHOD("didClose"), &GDScriptTextDocument::didClose);
ClassDB::bind_method(D_METHOD("didChange"), &GDScriptTextDocument::didChange);
ClassDB::bind_method(D_METHOD("didSave"), &GDScriptTextDocument::didSave);
ClassDB::bind_method(D_METHOD("nativeSymbol"), &GDScriptTextDocument::nativeSymbol);
ClassDB::bind_method(D_METHOD("documentSymbol"), &GDScriptTextDocument::documentSymbol);
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("foldingRange"), &GDScriptTextDocument::foldingRange);
ClassDB::bind_method(D_METHOD("codeLens"), &GDScriptTextDocument::codeLens);
ClassDB::bind_method(D_METHOD("documentLink"), &GDScriptTextDocument::documentLink);
Expand Down Expand Up @@ -79,6 +81,20 @@ void GDScriptTextDocument::didChange(const Variant &p_param) {
sync_script_content(doc.uri, doc.text);
}

void GDScriptTextDocument::didSave(const Variant &p_param) {
lsp::TextDocumentItem doc = load_document_item(p_param);
Dictionary dict = p_param;
String text = dict["text"];

sync_script_content(doc.uri, text);

/*String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(doc.uri);

Ref<GDScript> script = ResourceLoader::load(path);
script->load_source_code(path);
script->reload(true);*/
}

lsp::TextDocumentItem GDScriptTextDocument::load_document_item(const Variant &p_param) {
lsp::TextDocumentItem doc;
Dictionary params = p_param;
Expand Down Expand Up @@ -216,6 +232,14 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) {
return arr;
}

Dictionary GDScriptTextDocument::rename(const Dictionary &p_params) {
lsp::TextDocumentPositionParams params;
params.load(p_params);
String new_name = p_params["newName"];

return GDScriptLanguageProtocol::get_singleton()->get_workspace()->rename(params, new_name);
}

Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
lsp::CompletionItem item;
item.load(p_params);
Expand Down Expand Up @@ -406,7 +430,11 @@ GDScriptTextDocument::~GDScriptTextDocument() {
void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);

EditorFileSystem::get_singleton()->update_file(path);
Ref<GDScript> script = ResourceLoader::load(path);
script->load_source_code(path);
script->reload(true);
}

void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
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 @@ -45,6 +45,7 @@ class GDScriptTextDocument : public RefCounted {
void didOpen(const Variant &p_param);
void didClose(const Variant &p_param);
void didChange(const Variant &p_param);
void didSave(const Variant &p_param);

void sync_script_content(const String &p_path, const String &p_content);
void show_native_symbol_in_editor(const String &p_symbol_id);
Expand All @@ -61,6 +62,7 @@ class GDScriptTextDocument : public RefCounted {
Array documentSymbol(const Dictionary &p_params);
Array completion(const Dictionary &p_params);
Dictionary resolve(const Dictionary &p_params);
Dictionary rename(const Dictionary &p_params);
Array foldingRange(const Dictionary &p_params);
Array codeLens(const Dictionary &p_params);
Array documentLink(const Dictionary &p_params);
Expand Down
84 changes: 84 additions & 0 deletions modules/gdscript/language_server/gdscript_workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,36 @@ const lsp::DocumentSymbol *GDScriptWorkspace::get_script_symbol(const String &p_
return nullptr;
}

const lsp::DocumentSymbol *GDScriptWorkspace::get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier) {
for (int i = 0; i < p_parent->children.size(); ++i) {
const lsp::DocumentSymbol *parameter_symbol = &p_parent->children[i];
if (!parameter_symbol->detail.is_empty() && parameter_symbol->name == symbol_identifier) {
return parameter_symbol;
}
}

return nullptr;
}

const lsp::DocumentSymbol *GDScriptWorkspace::get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier) {
const lsp::DocumentSymbol *class_symbol = &p_parser->get_symbols();

for (int i = 0; i < class_symbol->children.size(); ++i) {
if (class_symbol->children[i].kind == lsp::SymbolKind::Function || class_symbol->children[i].kind == lsp::SymbolKind::Class) {
const lsp::DocumentSymbol *function_symbol = &class_symbol->children[i];

for (int l = 0; l < function_symbol->children.size(); ++l) {
const lsp::DocumentSymbol *local = &function_symbol->children[l];
if (!local->detail.is_empty() && local->name == p_symbol_identifier) {
return local;
}
}
}
}

return nullptr;
}

void GDScriptWorkspace::reload_all_workspace_scripts() {
List<String> paths;
list_script_files("res://", paths);
Expand Down Expand Up @@ -351,6 +381,50 @@ Error GDScriptWorkspace::parse_script(const String &p_path, const String &p_cont
return err;
}

Dictionary GDScriptWorkspace::rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name) {
Error err;
String path = get_file_path(p_doc_pos.textDocument.uri);

lsp::WorkspaceEdit edit;

List<String> paths;
list_script_files("res://", paths);

const lsp::DocumentSymbol *reference_symbol = resolve_symbol(p_doc_pos);
if (reference_symbol) {
String identifier = reference_symbol->name;

for (List<String>::Element *PE = paths.front(); PE; PE = PE->next()) {
PackedStringArray content = FileAccess::get_file_as_string(PE->get(), &err).split("\n");
for (int i = 0; i < content.size(); ++i) {
String line = content[i];

int character = line.find(identifier);
while (character > -1) {
lsp::TextDocumentPositionParams params;

lsp::TextDocumentIdentifier text_doc;
text_doc.uri = get_file_uri(PE->get());

params.textDocument = text_doc;
params.position.line = i;
params.position.character = character;

const lsp::DocumentSymbol *other_symbol = resolve_symbol(params);

if (other_symbol == reference_symbol) {
edit.add_change(text_doc.uri, i, character, character + identifier.length(), new_name);
}

character = line.find(identifier, character + 1);
}
}
}
}

return edit.to_json();
}

Error GDScriptWorkspace::parse_local_script(const String &p_path) {
Error err;
String content = FileAccess::get_file_as_string(p_path, &err);
Expand Down Expand Up @@ -479,10 +553,16 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
String target_script_path = path;
if (!ret.script.is_null()) {
target_script_path = ret.script->get_path();
} else if (!ret.class_path.is_empty()) {
target_script_path = ret.class_path;
}

if (const ExtendGDScriptParser *target_parser = get_parse_result(target_script_path)) {
symbol = target_parser->get_symbol_defined_at_line(LINE_NUMBER_TO_INDEX(ret.location));

if (symbol && symbol->kind == lsp::SymbolKind::Function && symbol->name != symbol_identifier) {
symbol = get_parameter_symbol(symbol, symbol_identifier);
}
}

} else {
Expand All @@ -494,6 +574,10 @@ const lsp::DocumentSymbol *GDScriptWorkspace::resolve_symbol(const lsp::TextDocu
}
} else {
symbol = parser->get_member_symbol(symbol_identifier);

if (!symbol) {
symbol = get_local_symbol(parser, symbol_identifier);
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions modules/gdscript/language_server/gdscript_workspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class GDScriptWorkspace : public RefCounted {

const lsp::DocumentSymbol *get_native_symbol(const String &p_class, const String &p_member = "") const;
const lsp::DocumentSymbol *get_script_symbol(const String &p_path) const;
const lsp::DocumentSymbol *get_parameter_symbol(const lsp::DocumentSymbol *p_parent, const String &symbol_identifier);
const lsp::DocumentSymbol *get_local_symbol(const ExtendGDScriptParser *p_parser, const String &p_symbol_identifier);

void reload_all_workspace_scripts();

Expand Down Expand Up @@ -90,6 +92,7 @@ class GDScriptWorkspace : public RefCounted {
Dictionary generate_script_api(const String &p_path);
Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
void did_delete_files(const Dictionary &p_params);
Dictionary rename(const lsp::TextDocumentPositionParams &p_doc_pos, const String &new_name);

GDScriptWorkspace();
~GDScriptWorkspace();
Expand Down
60 changes: 58 additions & 2 deletions modules/gdscript/language_server/lsp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,62 @@ struct TextEdit {
String newText;
};

/**
* The edits to be applied.
*/
struct WorkspaceEdit {
/**
* Holds changes to existing resources.
*/
Map<String, Vector<TextEdit>> changes;

_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;

Dictionary out_changes;
for (Map<String, Vector<TextEdit>>::Element *E = changes.front(); E; E = E->next()) {
Array edits;
for (int i = 0; i < E->get().size(); ++i) {
Dictionary text_edit;
text_edit["range"] = E->get()[i].range.to_json();
text_edit["newText"] = E->get()[i].newText;
edits.push_back(text_edit);
}
out_changes[E->key()] = edits;
}
dict["changes"] = out_changes;

return dict;
}

_FORCE_INLINE_ void add_change(const String &uri, const int &line, const int &start_character, const int &end_character, const String &new_text) {
if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) {
Vector<TextEdit> edit_list = E->value();
for (int i = 0; i < edit_list.size(); ++i) {
TextEdit edit = edit_list[i];
if (edit.range.start.character == start_character) {
return;
}
}
}

TextEdit new_edit;
new_edit.newText = new_text;
new_edit.range.start.line = line;
new_edit.range.start.character = start_character;
new_edit.range.end.line = line;
new_edit.range.end.character = end_character;

if (Map<String, Vector<TextEdit>>::Element *E = changes.find(uri)) {
E->value().push_back(new_edit);
} else {
Vector<TextEdit> edit_list;
edit_list.push_back(new_edit);
changes.insert(uri, edit_list);
}
}
};

/**
* Represents a reference to a command.
* Provides a title which will be used to represent a command in the UI.
Expand Down Expand Up @@ -486,15 +542,15 @@ struct TextDocumentSyncOptions {
* If present save notifications are sent to the server. If omitted the notification should not be
* sent.
*/
bool save = false;
SaveOptions save;

Dictionary to_json() {
Dictionary dict;
dict["willSaveWaitUntil"] = willSaveWaitUntil;
dict["willSave"] = willSave;
dict["openClose"] = openClose;
dict["change"] = change;
dict["save"] = save;
dict["save"] = save.to_json();
return dict;
}
};
Expand Down