Răsfoiți Sursa

Merge pull request #72259 from Paulb23/json-editing

Support editing JSON in ScriptEditor
Rémi Verschelde 2 ani în urmă
părinte
comite
bed1ebd527

+ 72 - 10
editor/plugins/script_editor_plugin.cpp

@@ -33,6 +33,7 @@
 #include "core/config/project_settings.h"
 #include "core/input/input.h"
 #include "core/io/file_access.h"
+#include "core/io/json.h"
 #include "core/io/resource_loader.h"
 #include "core/os/keyboard.h"
 #include "core/os/os.h"
@@ -209,6 +210,27 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const {
 	return syntax_highlighter;
 }
 
+////
+
+void EditorJSONSyntaxHighlighter::_update_cache() {
+	highlighter->set_text_edit(text_edit);
+	highlighter->clear_keyword_colors();
+	highlighter->clear_member_keyword_colors();
+	highlighter->clear_color_regions();
+
+	highlighter->set_symbol_color(EDITOR_GET("text_editor/theme/highlighting/symbol_color"));
+	highlighter->set_number_color(EDITOR_GET("text_editor/theme/highlighting/number_color"));
+
+	const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+	highlighter->add_color_region("\"", "\"", string_color);
+}
+
+Ref<EditorSyntaxHighlighter> EditorJSONSyntaxHighlighter::_create() const {
+	Ref<EditorJSONSyntaxHighlighter> syntax_highlighter;
+	syntax_highlighter.instantiate();
+	return syntax_highlighter;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 /*** SCRIPT EDITOR ****/
@@ -702,9 +724,10 @@ void ScriptEditor::_open_recent_script(int p_idx) {
 	if (FileAccess::exists(path)) {
 		List<String> extensions;
 		ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
+		ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
 
 		if (extensions.find(path.get_extension())) {
-			Ref<Script> scr = ResourceLoader::load(path);
+			Ref<Resource> scr = ResourceLoader::load(path);
 			if (scr.is_valid()) {
 				edit(scr, true);
 				return;
@@ -1182,6 +1205,7 @@ void ScriptEditor::_menu_option(int p_option) {
 
 			List<String> extensions;
 			ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
+			ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
 			bool built_in = !path.is_resource_file();
 
 			if (extensions.find(path.get_extension()) || built_in) {
@@ -1196,7 +1220,7 @@ void ScriptEditor::_menu_option(int p_option) {
 					}
 				}
 
-				Ref<Script> scr = ResourceLoader::load(path);
+				Ref<Resource> scr = ResourceLoader::load(path);
 				if (!scr.is_valid()) {
 					EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!"));
 					file_dialog_option = -1;
@@ -2319,12 +2343,23 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
 		}
 		se->add_syntax_highlighter(highlighter);
 
-		if (scr != nullptr && !highlighter_set) {
-			PackedStringArray languages = highlighter->_get_supported_languages();
+		if (highlighter_set) {
+			continue;
+		}
+
+		PackedStringArray languages = highlighter->_get_supported_languages();
+		// If script try language, else use extension.
+		if (scr != nullptr) {
 			if (languages.has(scr->get_language()->get_name())) {
 				se->set_syntax_highlighter(highlighter);
 				highlighter_set = true;
 			}
+			continue;
+		}
+
+		if (languages.has(p_resource->get_path().get_extension())) {
+			se->set_syntax_highlighter(highlighter);
+			highlighter_set = true;
 		}
 	}
 
@@ -2536,6 +2571,14 @@ void ScriptEditor::reload_scripts(bool p_refresh_only) {
 				scr->reload(true);
 			}
 
+			Ref<JSON> json = edited_res;
+			if (json != nullptr) {
+				Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
+				ERR_CONTINUE(!rel_json.is_valid());
+				json->parse(rel_json->get_parsed_text(), true);
+				json->set_last_modified_time(rel_json->get_last_modified_time());
+			}
+
 			Ref<TextFile> text_file = edited_res;
 			if (text_file.is_valid()) {
 				text_file->reload_from_file();
@@ -2564,8 +2607,9 @@ void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const
 Ref<Resource> ScriptEditor::open_file(const String &p_file) {
 	List<String> extensions;
 	ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
+	ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
 	if (extensions.find(p_file.get_extension())) {
-		Ref<Script> scr = ResourceLoader::load(p_file);
+		Ref<Resource> scr = ResourceLoader::load(p_file);
 		if (!scr.is_valid()) {
 			EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!"));
 			return Ref<Resource>();
@@ -2866,8 +2910,8 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data
 			if (file.is_empty() || !FileAccess::exists(file)) {
 				continue;
 			}
-			if (ResourceLoader::exists(file, "Script")) {
-				Ref<Script> scr = ResourceLoader::load(file);
+			if (ResourceLoader::exists(file, "Script") || ResourceLoader::exists(file, "JSON")) {
+				Ref<Resource> scr = ResourceLoader::load(file);
 				if (scr.is_valid()) {
 					return true;
 				}
@@ -2947,7 +2991,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co
 				continue;
 			}
 
-			if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) {
+			if (!ResourceLoader::exists(file, "Script") && !ResourceLoader::exists(file, "JSON") && !textfile_extensions.has(file.get_extension())) {
 				continue;
 			}
 
@@ -3105,6 +3149,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
 	HashSet<String> loaded_scripts;
 	List<String> extensions;
 	ResourceLoader::get_recognized_extensions_for_type("Script", &extensions);
+	ResourceLoader::get_recognized_extensions_for_type("JSON", &extensions);
 
 	for (int i = 0; i < scripts.size(); i++) {
 		String path = scripts[i];
@@ -3123,7 +3168,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
 		loaded_scripts.insert(path);
 
 		if (extensions.find(path.get_extension())) {
-			Ref<Script> scr = ResourceLoader::load(path);
+			Ref<Resource> scr = ResourceLoader::load(path);
 			if (!scr.is_valid()) {
 				continue;
 			}
@@ -3477,6 +3522,12 @@ void ScriptEditor::_open_script_request(const String &p_path) {
 		return;
 	}
 
+	Ref<JSON> json = ResourceLoader::load(p_path);
+	if (json.is_valid()) {
+		script_editor->edit(json, false);
+		return;
+	}
+
 	Error err;
 	Ref<TextFile> text_file = script_editor->_load_text_file(p_path, &err);
 	if (text_file.is_valid()) {
@@ -3539,7 +3590,8 @@ void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_numb
 			return;
 		} else {
 			Ref<Script> scr = res;
-			if (scr.is_valid()) {
+			Ref<JSON> json = res;
+			if (scr.is_valid() || json.is_valid()) {
 				edit(scr);
 
 				ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(_get_current_editor());
@@ -3942,6 +3994,10 @@ ScriptEditor::ScriptEditor() {
 
 	add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles")));
 	tab_container->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles")));
+
+	Ref<EditorJSONSyntaxHighlighter> json_syntax_highlighter;
+	json_syntax_highlighter.instantiate();
+	register_syntax_highlighter(json_syntax_highlighter);
 }
 
 ScriptEditor::~ScriptEditor() {
@@ -3963,6 +4019,8 @@ void ScriptEditorPlugin::edit(Object *p_object) {
 			}
 		}
 		script_editor->edit(p_script);
+	} else if (Object::cast_to<JSON>(p_object)) {
+		script_editor->edit(Object::cast_to<JSON>(p_object));
 	} else if (Object::cast_to<TextFile>(p_object)) {
 		script_editor->edit(Object::cast_to<TextFile>(p_object));
 	}
@@ -3977,6 +4035,10 @@ bool ScriptEditorPlugin::handles(Object *p_object) const {
 		return true;
 	}
 
+	if (Object::cast_to<JSON>(p_object)) {
+		return true;
+	}
+
 	return p_object->is_class("Script");
 }
 

+ 18 - 0
editor/plugins/script_editor_plugin.h

@@ -96,6 +96,24 @@ public:
 	virtual Ref<EditorSyntaxHighlighter> _create() const override;
 };
 
+class EditorJSONSyntaxHighlighter : public EditorSyntaxHighlighter {
+	GDCLASS(EditorJSONSyntaxHighlighter, EditorSyntaxHighlighter)
+
+private:
+	Ref<CodeHighlighter> highlighter;
+
+public:
+	virtual void _update_cache() override;
+	virtual Dictionary _get_line_syntax_highlighting_impl(int p_line) override { return highlighter->get_line_syntax_highlighting(p_line); }
+
+	virtual PackedStringArray _get_supported_languages() const override { return PackedStringArray{ "json" }; }
+	virtual String _get_name() const override { return TTR("JSON"); }
+
+	virtual Ref<EditorSyntaxHighlighter> _create() const override;
+
+	EditorJSONSyntaxHighlighter() { highlighter.instantiate(); }
+};
+
 ///////////////////////////////////////////////////////////////////////////////
 
 class ScriptEditorQuickOpen : public ConfirmationDialog {

+ 58 - 13
editor/plugins/text_editor.cpp

@@ -30,6 +30,7 @@
 
 #include "text_editor.h"
 
+#include "core/io/json.h"
 #include "core/os/keyboard.h"
 #include "editor/editor_node.h"
 #include "editor/editor_settings.h"
@@ -67,12 +68,12 @@ void TextEditor::_load_theme_settings() {
 String TextEditor::get_name() {
 	String name;
 
-	name = text_file->get_path().get_file();
+	name = edited_res->get_path().get_file();
 	if (name.is_empty()) {
 		// This appears for newly created built-in text_files before saving the scene.
 		name = TTR("[unsaved]");
-	} else if (text_file->is_built_in()) {
-		const String &text_file_name = text_file->get_name();
+	} else if (edited_res->is_built_in()) {
+		const String &text_file_name = edited_res->get_name();
 		if (!text_file_name.is_empty()) {
 			// If the built-in text_file has a custom resource name defined,
 			// display the built-in text_file name as follows: `ResourceName (scene_file.tscn)`
@@ -88,20 +89,29 @@ String TextEditor::get_name() {
 }
 
 Ref<Texture2D> TextEditor::get_theme_icon() {
-	return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), "");
+	return EditorNode::get_singleton()->get_object_icon(edited_res.ptr(), "TextFile");
 }
 
 Ref<Resource> TextEditor::get_edited_resource() const {
-	return text_file;
+	return edited_res;
 }
 
 void TextEditor::set_edited_resource(const Ref<Resource> &p_res) {
-	ERR_FAIL_COND(text_file.is_valid());
+	ERR_FAIL_COND(edited_res.is_valid());
 	ERR_FAIL_COND(p_res.is_null());
 
-	text_file = p_res;
+	edited_res = p_res;
+
+	Ref<TextFile> text_file = edited_res;
+	if (text_file != nullptr) {
+		code_editor->get_text_editor()->set_text(text_file->get_text());
+	}
+
+	Ref<JSON> json_file = edited_res;
+	if (json_file != nullptr) {
+		code_editor->get_text_editor()->set_text(json_file->get_parsed_text());
+	}
 
-	code_editor->get_text_editor()->set_text(text_file->get_text());
 	code_editor->get_text_editor()->clear_undo_history();
 	code_editor->get_text_editor()->tag_saved_version();
 
@@ -118,6 +128,8 @@ void TextEditor::enable_editor(Control *p_shortcut_context) {
 
 	_load_theme_settings();
 
+	_validate_script();
+
 	if (p_shortcut_context) {
 		for (int i = 0; i < edit_hb->get_child_count(); ++i) {
 			Control *c = cast_to<Control>(edit_hb->get_child(i));
@@ -143,7 +155,7 @@ PackedInt32Array TextEditor::get_breakpoints() {
 }
 
 void TextEditor::reload_text() {
-	ERR_FAIL_COND(text_file.is_null());
+	ERR_FAIL_COND(edited_res.is_null());
 
 	CodeEdit *te = code_editor->get_text_editor();
 	int column = te->get_caret_column();
@@ -151,7 +163,16 @@ void TextEditor::reload_text() {
 	int h = te->get_h_scroll();
 	int v = te->get_v_scroll();
 
-	te->set_text(text_file->get_text());
+	Ref<TextFile> text_file = edited_res;
+	if (text_file != nullptr) {
+		te->set_text(text_file->get_text());
+	}
+
+	Ref<JSON> json_file = edited_res;
+	if (json_file != nullptr) {
+		te->set_text(json_file->get_parsed_text());
+	}
+
 	te->set_caret_line(row);
 	te->set_caret_column(column);
 	te->set_h_scroll(h);
@@ -166,6 +187,20 @@ void TextEditor::reload_text() {
 void TextEditor::_validate_script() {
 	emit_signal(SNAME("name_changed"));
 	emit_signal(SNAME("edited_script_changed"));
+
+	Ref<JSON> json_file = edited_res;
+	if (json_file != nullptr) {
+		CodeEdit *te = code_editor->get_text_editor();
+
+		te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0));
+		code_editor->set_error("");
+
+		if (json_file->parse(te->get_text(), true) != OK) {
+			code_editor->set_error(json_file->get_error_message());
+			code_editor->set_error_pos(json_file->get_error_line(), 0);
+			te->set_line_background_color(code_editor->get_error_pos().x, EDITOR_GET("text_editor/theme/highlighting/mark_color"));
+		}
+	}
 }
 
 void TextEditor::_update_bookmark_list() {
@@ -204,13 +239,22 @@ void TextEditor::_bookmark_item_pressed(int p_idx) {
 }
 
 void TextEditor::apply_code() {
-	text_file->set_text(code_editor->get_text_editor()->get_text());
+	Ref<TextFile> text_file = edited_res;
+	if (text_file != nullptr) {
+		text_file->set_text(code_editor->get_text_editor()->get_text());
+	}
+
+	Ref<JSON> json_file = edited_res;
+	if (json_file != nullptr) {
+		json_file->parse(code_editor->get_text_editor()->get_text(), true);
+	}
+	code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
 }
 
 bool TextEditor::is_unsaved() {
 	const bool unsaved =
 			code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() ||
-			text_file->get_path().is_empty(); // In memory.
+			edited_res->get_path().is_empty(); // In memory.
 	return unsaved;
 }
 
@@ -431,7 +475,7 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
 }
 
 static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) {
-	if (Object::cast_to<TextFile>(*p_resource)) {
+	if (Object::cast_to<TextFile>(*p_resource) || Object::cast_to<JSON>(*p_resource)) {
 		return memnew(TextEditor);
 	}
 	return nullptr;
@@ -656,4 +700,5 @@ TextEditor::~TextEditor() {
 }
 
 void TextEditor::validate() {
+	this->code_editor->validate_script();
 }

+ 1 - 1
editor/plugins/text_editor.h

@@ -41,7 +41,7 @@ class TextEditor : public ScriptEditorBase {
 private:
 	CodeTextEditor *code_editor = nullptr;
 
-	Ref<TextFile> text_file;
+	Ref<Resource> edited_res;
 	bool editor_enabled = false;
 
 	HBoxContainer *edit_hb = nullptr;