Browse Source

Implement didSave notification and rename request

Francois Belair 4 years ago
parent
commit
7c5335081c

+ 1 - 0
core/object/script_language.h

@@ -310,6 +310,7 @@ public:
 		Ref<Script> script;
 		String class_name;
 		String class_member;
+		String class_path;
 		int location;
 	};
 

+ 1 - 0
modules/gdscript/gdscript_editor.cpp

@@ -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;

+ 28 - 0
modules/gdscript/language_server/gdscript_text_document.cpp

@@ -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);
@@ -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;
@@ -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);
@@ -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) {

+ 2 - 0
modules/gdscript/language_server/gdscript_text_document.h

@@ -45,6 +45,7 @@ protected:
 	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);
@@ -61,6 +62,7 @@ public:
 	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);

+ 84 - 0
modules/gdscript/language_server/gdscript_workspace.cpp

@@ -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);
@@ -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);
@@ -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 {
@@ -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);
+					}
 				}
 			}
 		}

+ 3 - 0
modules/gdscript/language_server/gdscript_workspace.h

@@ -52,6 +52,8 @@ protected:
 
 	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();
 
@@ -90,6 +92,7 @@ public:
 	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();

+ 58 - 2
modules/gdscript/language_server/lsp.hpp

@@ -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.
@@ -486,7 +542,7 @@ 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;
@@ -494,7 +550,7 @@ struct TextDocumentSyncOptions {
 		dict["willSave"] = willSave;
 		dict["openClose"] = openClose;
 		dict["change"] = change;
-		dict["save"] = save;
+		dict["save"] = save.to_json();
 		return dict;
 	}
 };