summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editor/editor_resource_picker.cpp20
-rw-r--r--editor/editor_resource_picker.h4
-rw-r--r--editor/import/resource_importer_obj.h3
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp75
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.h18
-rw-r--r--main/main.cpp2
-rw-r--r--scene/gui/code_edit.cpp23
-rw-r--r--scene/gui/code_edit.h2
-rw-r--r--scene/gui/tab_container.cpp2
-rw-r--r--scene/resources/font.cpp51
-rw-r--r--scene/resources/material.cpp6
-rw-r--r--tests/test_code_edit.h2255
-rw-r--r--tests/test_macros.h37
13 files changed, 2454 insertions, 44 deletions
diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp
index a4ab749db4..31c62880e2 100644
--- a/editor/editor_resource_picker.cpp
+++ b/editor/editor_resource_picker.cpp
@@ -135,6 +135,10 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
_update_resource();
}
+void EditorResourcePicker::_file_quick_selected() {
+ _file_selected(quick_open->get_selected());
+}
+
void EditorResourcePicker::_update_menu() {
_update_menu_items();
@@ -153,7 +157,10 @@ void EditorResourcePicker::_update_menu_items() {
// Add options for creating specific subtypes of the base resource type.
set_create_options(edit_menu);
- // Add an option to load a resource from a file.
+ // Add an option to load a resource from a file using the QuickOpen dialog.
+ edit_menu->add_icon_item(get_theme_icon(SNAME("Load"), SNAME("EditorIcons")), TTR("Quick Load"), OBJ_MENU_QUICKLOAD);
+
+ // Add an option to load a resource from a file using the regular file dialog.
edit_menu->add_icon_item(get_theme_icon(SNAME("Load"), SNAME("EditorIcons")), TTR("Load"), OBJ_MENU_LOAD);
// Add options for changing existing value of the resource.
@@ -246,6 +253,17 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
file_dialog->popup_file_dialog();
} break;
+ case OBJ_MENU_QUICKLOAD: {
+ if (!quick_open) {
+ quick_open = memnew(EditorQuickOpen);
+ add_child(quick_open);
+ quick_open->connect("quick_open", callable_mp(this, &EditorResourcePicker::_file_quick_selected));
+ }
+
+ quick_open->popup_dialog(base_type);
+ quick_open->set_title(TTR("Resource"));
+ } break;
+
case OBJ_MENU_EDIT: {
if (edited_resource.is_valid()) {
emit_signal(SNAME("resource_selected"), edited_resource);
diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h
index d77c31f831..d0dad0287b 100644
--- a/editor/editor_resource_picker.h
+++ b/editor/editor_resource_picker.h
@@ -32,6 +32,7 @@
#define EDITOR_RESOURCE_PICKER_H
#include "editor_file_dialog.h"
+#include "quick_open.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
@@ -54,9 +55,11 @@ class EditorResourcePicker : public HBoxContainer {
TextureRect *preview_rect;
Button *edit_button;
EditorFileDialog *file_dialog = nullptr;
+ EditorQuickOpen *quick_open = nullptr;
enum MenuOption {
OBJ_MENU_LOAD,
+ OBJ_MENU_QUICKLOAD,
OBJ_MENU_EDIT,
OBJ_MENU_CLEAR,
OBJ_MENU_MAKE_UNIQUE,
@@ -75,6 +78,7 @@ class EditorResourcePicker : public HBoxContainer {
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
void _resource_selected();
+ void _file_quick_selected();
void _file_selected(const String &p_path);
void _update_menu();
diff --git a/editor/import/resource_importer_obj.h b/editor/import/resource_importer_obj.h
index 414e0c1fe6..1bb5ef33ce 100644
--- a/editor/import/resource_importer_obj.h
+++ b/editor/import/resource_importer_obj.h
@@ -64,6 +64,9 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const Map<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ // Threaded import can currently cause deadlocks, see GH-48265.
+ virtual bool can_import_threaded() const override { return false; }
+
ResourceImporterOBJ();
};
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 030d90eeca..24cb660f7a 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -66,9 +66,13 @@ void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_scrip
_update_options_menu();
}
-void AnimationNodeBlendTreeEditor::_update_options_menu() {
+void AnimationNodeBlendTreeEditor::_update_options_menu(bool p_has_input_ports) {
add_node->get_popup()->clear();
+ add_node->get_popup()->set_size(Size2i(-1, -1));
for (int i = 0; i < add_options.size(); i++) {
+ if (p_has_input_ports && add_options[i].input_port_count == 0) {
+ continue;
+ }
add_node->get_popup()->add_item(add_options[i].name, i);
}
@@ -309,6 +313,11 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
return;
}
+ if (!from_node.is_empty() && anode->get_input_count() == 0) {
+ from_node = "";
+ return;
+ }
+
Point2 instance_pos = graph->get_scroll_ofs();
if (use_popup_menu_position) {
instance_pos += popup_menu_position;
@@ -328,11 +337,51 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
undo_redo->create_action(TTR("Add Node to BlendTree"));
undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode, instance_pos / EDSCALE);
undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name);
+
+ if (!from_node.is_empty()) {
+ undo_redo->add_do_method(blend_tree.ptr(), "connect_node", name, 0, from_node);
+ from_node = "";
+ }
+ if (!to_node.is_empty() && to_slot != -1) {
+ undo_redo->add_do_method(blend_tree.ptr(), "connect_node", to_node, to_slot, name);
+ to_node = "";
+ to_slot = -1;
+ }
+
undo_redo->add_do_method(this, "_update_graph");
undo_redo->add_undo_method(this, "_update_graph");
undo_redo->commit_action();
}
+void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) {
+ _update_options_menu(p_has_input_ports);
+ use_popup_menu_position = true;
+ popup_menu_position = p_popup_position;
+ add_node->get_popup()->set_position(p_node_position);
+ add_node->get_popup()->popup();
+}
+
+void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) {
+ _popup(false, graph->get_local_mouse_position(), p_position);
+}
+
+void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) {
+ Ref<AnimationNode> node = blend_tree->get_node(p_from);
+ if (node.is_valid()) {
+ from_node = p_from;
+ _popup(true, p_release_position, graph->get_global_mouse_position());
+ }
+}
+
+void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) {
+ Ref<AnimationNode> node = blend_tree->get_node(p_to);
+ if (node.is_valid()) {
+ to_node = p_to;
+ to_slot = p_to_slot;
+ _popup(false, p_release_position, graph->get_global_mouse_position());
+ }
+}
+
void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which) {
updating = true;
undo_redo->create_action(TTR("Node Moved"));
@@ -431,14 +480,6 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request() {
undo_redo->commit_action();
}
-void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) {
- _update_options_menu();
- use_popup_menu_position = true;
- popup_menu_position = graph->get_local_mouse_position();
- add_node->get_popup()->set_position(p_position);
- add_node->get_popup()->popup();
-}
-
void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) {
GraphNode *gn = Object::cast_to<GraphNode>(p_node);
ERR_FAIL_COND(!gn);
@@ -890,6 +931,8 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
graph->connect("scroll_offset_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_scroll_changed));
graph->connect("delete_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_nodes_request));
graph->connect("popup_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_request));
+ graph->connect("connection_to_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_to_empty));
+ graph->connect("connection_from_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_from_empty));
float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity");
graph->set_minimap_opacity(graph_minimap_opacity);
@@ -905,13 +948,13 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu));
add_options.push_back(AddOption("Animation", "AnimationNodeAnimation"));
- add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot"));
- add_options.push_back(AddOption("Add2", "AnimationNodeAdd2"));
- add_options.push_back(AddOption("Add3", "AnimationNodeAdd3"));
- add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2"));
- add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3"));
- add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek"));
- add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale"));
+ add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2));
+ add_options.push_back(AddOption("Add2", "AnimationNodeAdd2", 2));
+ add_options.push_back(AddOption("Add3", "AnimationNodeAdd3", 3));
+ add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2", 2));
+ add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3", 3));
+ add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek", 1));
+ add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale", 1));
add_options.push_back(AddOption("Transition", "AnimationNodeTransition"));
add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree"));
add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D"));
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h
index 9f09069719..0fcafad40e 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.h
+++ b/editor/plugins/animation_blend_tree_editor_plugin.h
@@ -64,22 +64,28 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
Map<StringName, ProgressBar *> animations;
Vector<EditorProperty *> visible_properties;
+ String to_node = "";
+ int to_slot = -1;
+ String from_node = "";
+
void _update_graph();
struct AddOption {
String name;
String type;
Ref<Script> script;
- AddOption(const String &p_name = String(), const String &p_type = String()) :
+ int input_port_count;
+ AddOption(const String &p_name = String(), const String &p_type = String(), bool p_input_port_count = 0) :
name(p_name),
- type(p_type) {
+ type(p_type),
+ input_port_count(p_input_port_count) {
}
};
Vector<AddOption> add_options;
void _add_node(int p_idx);
- void _update_options_menu();
+ void _update_options_menu(bool p_has_input_ports = false);
static AnimationNodeBlendTreeEditor *singleton;
@@ -98,7 +104,6 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
void _anim_selected(int p_index, Array p_options, const String &p_node);
void _delete_request(const String &p_which);
void _delete_nodes_request();
- void _popup_request(const Vector2 &p_position);
bool _update_filters(const Ref<AnimationNode> &anode);
void _edit_filters(const String &p_which);
@@ -106,6 +111,11 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin {
void _filter_toggled();
Ref<AnimationNode> _filter_edit;
+ void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position);
+ void _popup_request(const Vector2 &p_position);
+ void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position);
+ void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position);
+
void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing);
void _removed_from_graph();
diff --git a/main/main.cpp b/main/main.cpp
index 5513e571d6..532b1e80c9 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -455,10 +455,10 @@ void Main::test_cleanup() {
ResourceLoader::remove_custom_loaders();
ResourceSaver::remove_custom_savers();
+ unregister_driver_types();
#ifdef TOOLS_ENABLED
EditorNode::unregister_editor_types();
#endif
- unregister_driver_types();
unregister_module_types();
unregister_platform_apis();
unregister_scene_types();
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e7769f9372..4dfd6902e6 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -467,7 +467,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
}
/* MISC */
- if (k->is_action("ui_cancel", true)) {
+ if (!code_hint.is_empty() && k->is_action("ui_cancel", true)) {
set_code_hint("");
accept_event();
return;
@@ -1725,14 +1725,17 @@ bool CodeEdit::is_code_completion_enabled() const {
void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) {
code_completion_prefixes.clear();
for (int i = 0; i < p_prefixes.size(); i++) {
- code_completion_prefixes.insert(p_prefixes[i]);
+ const String prefix = p_prefixes[i];
+
+ ERR_CONTINUE_MSG(prefix.is_empty(), "Code completion prefix cannot be empty.");
+ code_completion_prefixes.insert(prefix[0]);
}
}
TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
TypedArray<String> prefixes;
- for (Set<String>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
- prefixes.push_back(E->get());
+ for (const Set<char32_t>::Element *E = code_completion_prefixes.front(); E; E = E->next()) {
+ prefixes.push_back(String::chr(E->get()));
}
return prefixes;
}
@@ -1795,9 +1798,9 @@ void CodeEdit::request_code_completion(bool p_force) {
String line = get_line(get_caret_line());
int ofs = CLAMP(get_caret_column(), 0, line.length());
- if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(String::chr(line[ofs - 1])))) {
+ if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) {
emit_signal(SNAME("request_code_completion"));
- } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[ofs - 2]))) {
+ } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) {
emit_signal(SNAME("request_code_completion"));
}
}
@@ -1969,7 +1972,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
end_complex_operation();
cancel_code_completion();
- if (code_completion_prefixes.has(String::chr(last_completion_char))) {
+ if (code_completion_prefixes.has(last_completion_char)) {
request_code_completion();
}
}
@@ -2764,7 +2767,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
bool prev_is_word = false;
/* Cancel if we are at the close of a string. */
- if (in_string == -1 && first_quote_col == cofs - 1) {
+ if (caret_column > 0 && in_string == -1 && first_quote_col == cofs - 1) {
cancel_code_completion();
return;
/* In a string, therefore we are trying to complete the string text. */
@@ -2790,9 +2793,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* If all else fails, check for a prefix. */
/* Single space between caret and prefix is okay. */
bool prev_is_prefix = false;
- if (cofs > 0 && code_completion_prefixes.has(String::chr(line[cofs - 1]))) {
+ if (cofs > 0 && code_completion_prefixes.has(line[cofs - 1])) {
prev_is_prefix = true;
- } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(String::chr(line[cofs - 2]))) {
+ } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(line[cofs - 2])) {
prev_is_prefix = true;
}
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 740548d559..d8eccb7d32 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -214,7 +214,7 @@ private:
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
- Set<String> code_completion_prefixes;
+ Set<char32_t> code_completion_prefixes;
List<ScriptCodeCompletionOption> code_completion_option_submitted;
List<ScriptCodeCompletionOption> code_completion_option_sources;
String code_completion_base;
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 137ce7e96f..a423dc0173 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -538,7 +538,6 @@ void TabContainer::_notification(int p_what) {
void TabContainer::_draw_tab(Ref<StyleBox> &p_tab_style, Color &p_font_color, int p_index, float p_x) {
Control *control = get_tab_control(p_index);
RID canvas = get_canvas_item();
- Ref<Font> font = get_theme_font(SNAME("font"));
Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
int outline_size = get_theme_constant(SNAME("outline_size"));
int icon_text_distance = get_theme_constant(SNAME("icon_separation"));
@@ -1134,7 +1133,6 @@ Size2 TabContainer::get_minimum_size() const {
Ref<StyleBox> tab_unselected = get_theme_stylebox(SNAME("tab_unselected"));
Ref<StyleBox> tab_selected = get_theme_stylebox(SNAME("tab_selected"));
Ref<StyleBox> tab_disabled = get_theme_stylebox(SNAME("tab_disabled"));
- Ref<Font> font = get_theme_font(SNAME("font"));
if (tabs_visible) {
ms.y += MAX(MAX(tab_unselected->get_minimum_size().y, tab_selected->get_minimum_size().y), tab_disabled->get_minimum_size().y);
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 7af8e900fd..29bd56eebd 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -689,227 +689,272 @@ void FontData::remove_cache(int p_cache_index) {
}
Array FontData::get_size_cache_list(int p_cache_index) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Array());
_ensure_rid(p_cache_index);
return TS->font_get_size_cache_list(cache[p_cache_index]);
}
void FontData::clear_size_cache(int p_cache_index) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_clear_size_cache(cache[p_cache_index]);
}
void FontData::remove_size_cache(int p_cache_index, const Vector2i &p_size) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_remove_size_cache(cache[p_cache_index], p_size);
}
void FontData::set_variation_coordinates(int p_cache_index, const Dictionary &p_variation_coordinates) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_variation_coordinates(cache[p_cache_index], p_variation_coordinates);
emit_changed();
}
Dictionary FontData::get_variation_coordinates(int p_cache_index) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Dictionary());
_ensure_rid(p_cache_index);
return TS->font_get_variation_coordinates(cache[p_cache_index]);
}
void FontData::set_ascent(int p_cache_index, int p_size, real_t p_ascent) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_ascent(cache[p_cache_index], p_size, p_ascent);
}
real_t FontData::get_ascent(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0.f);
_ensure_rid(p_cache_index);
return TS->font_get_ascent(cache[p_cache_index], p_size);
}
void FontData::set_descent(int p_cache_index, int p_size, real_t p_descent) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_descent(cache[p_cache_index], p_size, p_descent);
}
real_t FontData::get_descent(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0.f);
_ensure_rid(p_cache_index);
return TS->font_get_descent(cache[p_cache_index], p_size);
}
void FontData::set_underline_position(int p_cache_index, int p_size, real_t p_underline_position) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_underline_position(cache[p_cache_index], p_size, p_underline_position);
}
real_t FontData::get_underline_position(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0.f);
_ensure_rid(p_cache_index);
return TS->font_get_underline_position(cache[p_cache_index], p_size);
}
void FontData::set_underline_thickness(int p_cache_index, int p_size, real_t p_underline_thickness) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_underline_thickness(cache[p_cache_index], p_size, p_underline_thickness);
}
real_t FontData::get_underline_thickness(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0.f);
_ensure_rid(p_cache_index);
return TS->font_get_underline_thickness(cache[p_cache_index], p_size);
}
void FontData::set_scale(int p_cache_index, int p_size, real_t p_scale) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_scale(cache[p_cache_index], p_size, p_scale);
}
real_t FontData::get_scale(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0.f);
_ensure_rid(p_cache_index);
return TS->font_get_scale(cache[p_cache_index], p_size);
}
void FontData::set_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing, int p_value) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_spacing(cache[p_cache_index], p_size, p_spacing, p_value);
}
int FontData::get_spacing(int p_cache_index, int p_size, TextServer::SpacingType p_spacing) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0);
_ensure_rid(p_cache_index);
return TS->font_get_spacing(cache[p_cache_index], p_size, p_spacing);
}
int FontData::get_texture_count(int p_cache_index, const Vector2i &p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0);
_ensure_rid(p_cache_index);
return TS->font_get_texture_count(cache[p_cache_index], p_size);
}
void FontData::clear_textures(int p_cache_index, const Vector2i &p_size) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_clear_textures(cache[p_cache_index], p_size);
}
void FontData::remove_texture(int p_cache_index, const Vector2i &p_size, int p_texture_index) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_remove_texture(cache[p_cache_index], p_size, p_texture_index);
}
void FontData::set_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index, const Ref<Image> &p_image) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_texture_image(cache[p_cache_index], p_size, p_texture_index, p_image);
}
Ref<Image> FontData::get_texture_image(int p_cache_index, const Vector2i &p_size, int p_texture_index) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Ref<Image>());
_ensure_rid(p_cache_index);
return TS->font_get_texture_image(cache[p_cache_index], p_size, p_texture_index);
}
void FontData::set_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index, const PackedInt32Array &p_offset) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_texture_offsets(cache[p_cache_index], p_size, p_texture_index, p_offset);
}
PackedInt32Array FontData::get_texture_offsets(int p_cache_index, const Vector2i &p_size, int p_texture_index) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, PackedInt32Array());
_ensure_rid(p_cache_index);
return TS->font_get_texture_offsets(cache[p_cache_index], p_size, p_texture_index);
}
Array FontData::get_glyph_list(int p_cache_index, const Vector2i &p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Array());
_ensure_rid(p_cache_index);
return TS->font_get_glyph_list(cache[p_cache_index], p_size);
}
void FontData::clear_glyphs(int p_cache_index, const Vector2i &p_size) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_clear_glyphs(cache[p_cache_index], p_size);
}
void FontData::remove_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_remove_glyph(cache[p_cache_index], p_size, p_glyph);
}
void FontData::set_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph, const Vector2 &p_advance) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_glyph_advance(cache[p_cache_index], p_size, p_glyph, p_advance);
}
Vector2 FontData::get_glyph_advance(int p_cache_index, int p_size, int32_t p_glyph) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Vector2());
_ensure_rid(p_cache_index);
return TS->font_get_glyph_advance(cache[p_cache_index], p_size, p_glyph);
}
void FontData::set_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_offset) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_glyph_offset(cache[p_cache_index], p_size, p_glyph, p_offset);
}
Vector2 FontData::get_glyph_offset(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Vector2());
_ensure_rid(p_cache_index);
return TS->font_get_glyph_offset(cache[p_cache_index], p_size, p_glyph);
}
void FontData::set_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Vector2 &p_gl_size) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_glyph_size(cache[p_cache_index], p_size, p_glyph, p_gl_size);
}
Vector2 FontData::get_glyph_size(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Vector2());
_ensure_rid(p_cache_index);
return TS->font_get_glyph_size(cache[p_cache_index], p_size, p_glyph);
}
void FontData::set_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, const Rect2 &p_uv_rect) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph, p_uv_rect);
}
Rect2 FontData::get_glyph_uv_rect(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Rect2());
_ensure_rid(p_cache_index);
return TS->font_get_glyph_uv_rect(cache[p_cache_index], p_size, p_glyph);
}
void FontData::set_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph, int p_texture_idx) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph, p_texture_idx);
}
int FontData::get_glyph_texture_idx(int p_cache_index, const Vector2i &p_size, int32_t p_glyph) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, 0);
_ensure_rid(p_cache_index);
return TS->font_get_glyph_texture_idx(cache[p_cache_index], p_size, p_glyph);
}
Array FontData::get_kerning_list(int p_cache_index, int p_size) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Array());
_ensure_rid(p_cache_index);
return TS->font_get_kerning_list(cache[p_cache_index], p_size);
}
void FontData::clear_kerning_map(int p_cache_index, int p_size) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_clear_kerning_map(cache[p_cache_index], p_size);
}
void FontData::remove_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_remove_kerning(cache[p_cache_index], p_size, p_glyph_pair);
}
void FontData::set_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_set_kerning(cache[p_cache_index], p_size, p_glyph_pair, p_kerning);
}
Vector2 FontData::get_kerning(int p_cache_index, int p_size, const Vector2i &p_glyph_pair) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, Vector2());
_ensure_rid(p_cache_index);
return TS->font_get_kerning(cache[p_cache_index], p_size, p_glyph_pair);
}
void FontData::render_range(int p_cache_index, const Vector2i &p_size, char32_t p_start, char32_t p_end) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_render_range(cache[p_cache_index], p_size, p_start, p_end);
}
void FontData::render_glyph(int p_cache_index, const Vector2i &p_size, int32_t p_index) {
+ ERR_FAIL_COND(p_cache_index < 0);
_ensure_rid(p_cache_index);
TS->font_render_glyph(cache[p_cache_index], p_size, p_index);
}
RID FontData::get_cache_rid(int p_cache_index) const {
+ ERR_FAIL_COND_V(p_cache_index < 0, RID());
_ensure_rid(p_cache_index);
return cache[p_cache_index];
}
@@ -1008,10 +1053,8 @@ void Font::_data_changed() {
void Font::_ensure_rid(int p_index) const {
// Find or create cache record.
- for (int i = 0; i < rids.size(); i++) {
- if (!rids[i].is_valid() && data[i].is_valid()) {
- rids.write[i] = data[i]->find_cache(variation_coordinates);
- }
+ if (!rids[p_index].is_valid() && data[p_index].is_valid()) {
+ rids.write[p_index] = data[p_index]->find_cache(variation_coordinates);
}
}
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 77a68151c4..643b0e34b9 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -790,12 +790,10 @@ void BaseMaterial3D::_update_shader() {
}
} break;
case BILLBOARD_FIXED_Y: {
- code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],WORLD_MATRIX[1],vec4(normalize(cross(CAMERA_MATRIX[0].xyz,WORLD_MATRIX[1].xyz)), 0.0),WORLD_MATRIX[3]);\n";
+ code += " MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(vec4(normalize(cross(vec3(0.0, 1.0, 0.0), CAMERA_MATRIX[2].xyz)),0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(normalize(cross(CAMERA_MATRIX[0].xyz, vec3(0.0, 1.0, 0.0))),0.0),WORLD_MATRIX[3]);\n";
if (flags[FLAG_BILLBOARD_KEEP_SCALE]) {
- code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, 1.0, 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
- } else {
- code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(1.0, 0.0, 0.0, 0.0),vec4(0.0, 1.0/length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0),vec4(0.0, 0.0, 0.0 ,1.0));\n";
+ code += " MODELVIEW_MATRIX = MODELVIEW_MATRIX * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0),vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0),vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0),vec4(0.0, 0.0, 0.0, 1.0));\n";
}
} break;
case BILLBOARD_PARTICLES: {
diff --git a/tests/test_code_edit.h b/tests/test_code_edit.h
index 9579d8ebef..fc8cec60af 100644
--- a/tests/test_code_edit.h
+++ b/tests/test_code_edit.h
@@ -808,6 +808,2261 @@ TEST_CASE("[SceneTree][CodeEdit] line gutters") {
memdelete(code_edit);
}
+TEST_CASE("[SceneTree][CodeEdit] delimiters") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ const Point2 OUTSIDE_DELIMETER = Point2(-1, -1);
+
+ code_edit->clear_string_delimiters();
+ code_edit->clear_comment_delimiters();
+
+ SUBCASE("[CodeEdit] add and remove delimiters") {
+ SUBCASE("[CodeEdit] add and remove string delimiters") {
+ /* Add a delimiter.*/
+ code_edit->add_string_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Adding a duplicate start key is not allowed. */
+ code_edit->add_string_delimiter("\"", "\'", false);
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Adding a duplicate end key is allowed. */
+ code_edit->add_string_delimiter("'", "\"", false);
+ CHECK(code_edit->has_string_delimiter("'"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Both start and end keys have to be symbols. */
+ code_edit->add_string_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("@", "f", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("@"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->add_string_delimiter("f", "f", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("f"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Blank start keys are not allowed */
+ code_edit->add_string_delimiter("", "#", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ ERR_PRINT_ON;
+
+ /* Blank end keys are allowed. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 3);
+
+ /* Remove a delimiter. */
+ code_edit->remove_string_delimiter("#");
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ /* Set should override existing, and test multiline */
+ TypedArray<String> delimiters;
+ delimiters.push_back("^^ ^^");
+
+ code_edit->set_string_delimiters(delimiters);
+ CHECK_FALSE(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->has_string_delimiter("^^"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* clear should remove all. */
+ code_edit->clear_string_delimiters();
+ CHECK_FALSE(code_edit->has_string_delimiter("^^"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+ }
+
+ SUBCASE("[CodeEdit] add and remove comment delimiters") {
+ /* Add a delimiter.*/
+ code_edit->add_comment_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Adding a duplicate start key is not allowed. */
+ code_edit->add_comment_delimiter("\"", "\'", false);
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Adding a duplicate end key is allowed. */
+ code_edit->add_comment_delimiter("'", "\"", false);
+ CHECK(code_edit->has_comment_delimiter("'"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Both start and end keys have to be symbols. */
+ code_edit->add_comment_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("f", "\"", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("@", "f", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("@"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->add_comment_delimiter("f", "f", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("f"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Blank start keys are not allowed. */
+ code_edit->add_comment_delimiter("", "#", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ ERR_PRINT_ON;
+
+ /* Blank end keys are allowed. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 3);
+
+ /* Remove a delimiter. */
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ /* Set should override existing, and test multiline. */
+ TypedArray<String> delimiters;
+ delimiters.push_back("^^ ^^");
+
+ code_edit->set_comment_delimiters(delimiters);
+ CHECK_FALSE(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->has_comment_delimiter("^^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* clear should remove all. */
+ code_edit->clear_comment_delimiters();
+ CHECK_FALSE(code_edit->has_comment_delimiter("^^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+ }
+
+ SUBCASE("[CodeEdit] add and remove mixed delimiters") {
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_OFF;
+
+ /* Disallow adding a string with the same start key as comment. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK_FALSE(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ code_edit->add_string_delimiter("\"", "\"", false);
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Disallow adding a comment with the same start key as string. */
+ code_edit->add_comment_delimiter("\"", "", false);
+ CHECK_FALSE(code_edit->has_comment_delimiter("\""));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ ERR_PRINT_ON;
+
+ /* Cannot remove string with remove comment. */
+ code_edit->remove_comment_delimiter("\"");
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Cannot remove comment with remove string. */
+ code_edit->remove_string_delimiter("#");
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Clear comments leave strings. */
+ code_edit->clear_comment_delimiters();
+ CHECK(code_edit->has_string_delimiter("\""));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Clear string leave comments. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ code_edit->clear_string_delimiters();
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+ }
+ }
+
+ SUBCASE("[CodeEdit] single line delimiters") {
+ SUBCASE("[CodeEdit] single line string delimiters") {
+ /* Blank end key should set lineonly to true. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Insert line above, line with string then line below. */
+ code_edit->insert_text_at_caret(" \n#\n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after start key is in string and start / end positions are correct. */
+ CHECK(code_edit->is_in_string(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(2, 1));
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check region metadata. */
+ int idx = code_edit->is_in_string(1, 1);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "");
+
+ /* Check nested strings are handeled correctly. */
+ code_edit->set_text(" \n# # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in string and start / end positions are correct. */
+ CHECK(code_edit->is_in_string(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first. */
+ CHECK(code_edit->is_in_string(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check is in string with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # # \n ");
+ CHECK(code_edit->is_in_string(1) != -1);
+
+ code_edit->set_text(" \n text # # \n ");
+ CHECK(code_edit->is_in_string(1) == -1);
+
+ /* Removing delimiter should update. */
+ code_edit->set_text(" \n # # \n ");
+
+ code_edit->remove_string_delimiter("#");
+ CHECK_FALSE(code_edit->has_string_delimiter("$"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_string(1) == -1);
+
+ /* Adding and clear should update. */
+ code_edit->add_string_delimiter("#", "", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+ CHECK(code_edit->is_in_string(1) != -1);
+
+ code_edit->clear_string_delimiters();
+ CHECK_FALSE(code_edit->has_string_delimiter("$"));
+ CHECK(code_edit->get_string_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_string(1) == -1);
+ }
+
+ SUBCASE("[CodeEdit] single line comment delimiters") {
+ /* Blank end key should set lineonly to true. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Insert line above, line with comment then line below. */
+ code_edit->insert_text_at_caret(" \n#\n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(2, 1));
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check region metadata. */
+ int idx = code_edit->is_in_comment(1, 1);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "");
+
+ /* Check nested comments are handeled correctly. */
+ code_edit->set_text(" \n# # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first. */
+ CHECK(code_edit->is_in_comment(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check is in comment with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # # \n ");
+ CHECK(code_edit->is_in_comment(1) != -1);
+
+ code_edit->set_text(" \n text # # \n ");
+ CHECK(code_edit->is_in_comment(1) == -1);
+
+ /* Removing delimiter should update. */
+ code_edit->set_text(" \n # # \n ");
+
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_comment(1) == -1);
+
+ /* Adding and clear should update. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+ CHECK(code_edit->is_in_comment(1) != -1);
+
+ code_edit->clear_comment_delimiters();
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ CHECK(code_edit->is_in_comment(1) == -1);
+ }
+
+ SUBCASE("[CodeEdit] single line mixed delimiters") {
+ /* Blank end key should set lineonly to true. */
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("&", "", false);
+ CHECK(code_edit->has_string_delimiter("&"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Nest a string delimiter inside a comment. */
+ code_edit->set_text(" \n# & \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before first start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column after the first start key is in comment and start / end positions are correct. */
+ CHECK(code_edit->is_in_comment(1, 1) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == Point2(6, 1));
+
+ /* Check column after the second start key returns data for the first, and does not state string. */
+ CHECK(code_edit->is_in_comment(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(1, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+ CHECK(code_edit->is_in_string(1, 5) == -1);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Remove the comment delimiter. */
+ code_edit->remove_comment_delimiter("#");
+ CHECK_FALSE(code_edit->has_comment_delimiter("$"));
+ CHECK(code_edit->get_comment_delimiters().size() == 0);
+
+ /* The "first" comment region is no longer valid. */
+ CHECK(code_edit->is_in_comment(1, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 1) == OUTSIDE_DELIMETER);
+
+ /* The "second" region as string is now valid. */
+ CHECK(code_edit->is_in_string(1, 5) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 5) == Point2(4, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 5) == Point2(6, 1));
+ }
+ }
+
+ SUBCASE("[CodeEdit] multiline delimiters") {
+ SUBCASE("[CodeEdit] multiline string delimiters") {
+ code_edit->clear_string_delimiters();
+ code_edit->clear_comment_delimiters();
+
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("#", "#", false);
+ CHECK(code_edit->has_string_delimiter("#"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* First test over a single line. */
+ code_edit->set_text(" \n # # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column before closing delimiter is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(5, 1));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(1, 6) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check the region metadata. */
+ int idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test over a multiple blank lines. */
+ code_edit->set_text(" \n # \n\n # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check blank middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* Next test over a multiple non-blank lines. */
+ code_edit->set_text(" \n # \n \n # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test nested strings. */
+ code_edit->add_string_delimiter("^", "^", false);
+ CHECK(code_edit->has_string_delimiter("^"));
+ CHECK(code_edit->get_string_delimiters().size() == 2);
+
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in string. */
+ CHECK(code_edit->is_in_string(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in string. */
+ CHECK(code_edit->is_in_string(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in string. */
+ CHECK(code_edit->is_in_string(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_string(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in string. */
+ CHECK(code_edit->is_in_string(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in string. */
+ CHECK(code_edit->is_in_string(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in string. */
+ CHECK(code_edit->is_in_string(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test no end key. */
+ code_edit->set_text(" \n # \n ");
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_string(1, 2);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(-1, -1));
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in string with no column retruns true if entire line is string excluding whitespace. */
+ code_edit->set_text(" \n # \n\n #\n ");
+ CHECK(code_edit->is_in_string(1) != -1);
+ CHECK(code_edit->is_in_string(2) != -1);
+ CHECK(code_edit->is_in_string(3) != -1);
+
+ code_edit->set_text(" \n test # \n\n # test \n ");
+ CHECK(code_edit->is_in_string(1) == -1);
+ CHECK(code_edit->is_in_string(2) != -1);
+ CHECK(code_edit->is_in_string(3) == -1);
+ }
+
+ SUBCASE("[CodeEdit] multiline comment delimiters") {
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "#", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* First test over a single line. */
+ code_edit->set_text(" \n # # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column before closing delimiter is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(5, 1));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 6) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 6) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 6) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(2, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(2, 1) == OUTSIDE_DELIMETER);
+
+ /* Check the region metadata. */
+ int idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test over a multiple blank lines. */
+ code_edit->set_text(" \n # \n\n # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check blank middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* Next test over a multiple non-blank lines. */
+ code_edit->set_text(" \n # \n \n # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(2, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(2, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(2, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test nested comments. */
+ code_edit->add_comment_delimiter("^", "^", false);
+ CHECK(code_edit->has_comment_delimiter("^"));
+ CHECK(code_edit->get_comment_delimiters().size() == 2);
+
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Next test no end key. */
+ code_edit->set_text(" \n # \n ");
+
+ /* check the region metadata. */
+ idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(-1, -1));
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in comment with no column retruns true if entire line is comment excluding whitespace. */
+ code_edit->set_text(" \n # \n\n #\n ");
+ CHECK(code_edit->is_in_comment(1) != -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) != -1);
+
+ code_edit->set_text(" \n test # \n\n # test \n ");
+ CHECK(code_edit->is_in_comment(1) == -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) == -1);
+ }
+
+ SUBCASE("[CodeEdit] multiline mixed delimiters") {
+ /* Add comment delimiter. */
+ code_edit->add_comment_delimiter("#", "#", false);
+ CHECK(code_edit->has_comment_delimiter("#"));
+ CHECK(code_edit->get_comment_delimiters().size() == 1);
+
+ /* Add string delimiter. */
+ code_edit->add_string_delimiter("^", "^", false);
+ CHECK(code_edit->has_string_delimiter("^"));
+ CHECK(code_edit->get_string_delimiters().size() == 1);
+
+ /* Nest a string inside a comment. */
+ code_edit->set_text(" \n # ^\n \n^ # \n ");
+
+ /* Check line above is not in comment. */
+ CHECK(code_edit->is_in_comment(0, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(0, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(0, 1) == OUTSIDE_DELIMETER);
+
+ /* Check column before start key is not in comment. */
+ CHECK(code_edit->is_in_comment(1, 0) == -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 0) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(1, 0) == OUTSIDE_DELIMETER);
+
+ /* Check column just after start key is in comment. */
+ CHECK(code_edit->is_in_comment(1, 2) != -1);
+ CHECK(code_edit->get_delimiter_start_position(1, 2) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(1, 2) == Point2(3, 3));
+
+ /* Check middle line. */
+ CHECK(code_edit->is_in_comment(2, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(2, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(2, 0) == Point2(3, 3));
+
+ /* Check column just before end key is in comment. */
+ CHECK(code_edit->is_in_comment(3, 0) != -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 0) == Point2(2, 1));
+ CHECK(code_edit->get_delimiter_end_position(3, 0) == Point2(3, 3));
+
+ /* Check column after end key is not in comment. */
+ CHECK(code_edit->is_in_comment(3, 3) == -1);
+ CHECK(code_edit->get_delimiter_start_position(3, 3) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(3, 3) == OUTSIDE_DELIMETER);
+
+ /* Check line after is not in comment. */
+ CHECK(code_edit->is_in_comment(4, 1) == -1);
+ CHECK(code_edit->get_delimiter_start_position(4, 1) == OUTSIDE_DELIMETER);
+ CHECK(code_edit->get_delimiter_end_position(4, 1) == OUTSIDE_DELIMETER);
+
+ /* check the region metadata. */
+ int idx = code_edit->is_in_comment(1, 2);
+ CHECK(code_edit->get_delimiter_start_key(idx) == "#");
+ CHECK(code_edit->get_delimiter_end_key(idx) == "#");
+
+ /* Check is in comment with no column retruns true as inner delimiter should not be counted. */
+ CHECK(code_edit->is_in_comment(1) != -1);
+ CHECK(code_edit->is_in_comment(2) != -1);
+ CHECK(code_edit->is_in_comment(3) != -1);
+ }
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] indent") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] indent settings") {
+ code_edit->set_indent_size(10);
+ CHECK(code_edit->get_indent_size() == 10);
+ CHECK(code_edit->get_tab_size() == 10);
+
+ code_edit->set_auto_indent_enabled(false);
+ CHECK_FALSE(code_edit->is_auto_indent_enabled());
+
+ code_edit->set_auto_indent_enabled(true);
+ CHECK(code_edit->is_auto_indent_enabled());
+
+ code_edit->set_indent_using_spaces(false);
+ CHECK_FALSE(code_edit->is_indent_using_spaces());
+
+ code_edit->set_indent_using_spaces(true);
+ CHECK(code_edit->is_indent_using_spaces());
+
+ /* Only the first char is registered. */
+ TypedArray<String> auto_indent_prefixes;
+ auto_indent_prefixes.push_back("::");
+ auto_indent_prefixes.push_back("s");
+ auto_indent_prefixes.push_back("1");
+ code_edit->set_auto_indent_prefixes(auto_indent_prefixes);
+
+ auto_indent_prefixes = code_edit->get_auto_indent_prefixes();
+ CHECK(auto_indent_prefixes.has(":"));
+ CHECK(auto_indent_prefixes.has("s"));
+ CHECK(auto_indent_prefixes.has("1"));
+ }
+
+ SUBCASE("[CodeEdit] indent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Do nothing if not editable. */
+ code_edit->set_editable(false);
+
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->set_editable(true);
+
+ /* Simple indent. */
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ /* Check input action. */
+ SEND_GUI_ACTION(code_edit, "ui_text_indent");
+ CHECK(code_edit->get_line(0) == "\t\t");
+
+ /* Insert in place. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "test\t");
+
+ /* Indent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Selection does entire line. */
+ code_edit->set_text("test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("test\ntext");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "\ttext");
+
+ /* Do not indent line if last col is zero. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Indent even if last column of first line. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 4, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("test");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_indent();
+ CHECK(code_edit->get_selection_from_column() == 2);
+ CHECK(code_edit->get_selection_to_column() == 3);
+ CHECK(code_edit->get_line(0) == "\ttest");
+ code_edit->undo();
+ }
+
+ SUBCASE("[CodeEdit] indent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Do nothing if not editable. */
+ code_edit->set_editable(false);
+
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0).is_empty());
+
+ code_edit->set_editable(true);
+
+ /* Simple indent. */
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Check input action. */
+ SEND_GUI_ACTION(code_edit, "ui_text_indent");
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Insert in place. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == "test ");
+
+ /* Indent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test");
+ code_edit->indent_lines();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Selection does entire line. */
+ code_edit->set_text("test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* single indent only add required spaces. */
+ code_edit->set_text(" test");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("test\ntext");
+ code_edit->select_all();
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == " text");
+
+ /* Do not indent line if last col is zero. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Indent even if last column of first line. */
+ code_edit->set_text("test\ntext");
+ code_edit->select(0, 4, 1, 0);
+ code_edit->do_indent();
+ CHECK(code_edit->get_line(0) == " test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("test");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_indent();
+ CHECK(code_edit->get_selection_from_column() == 5);
+ CHECK(code_edit->get_selection_to_column() == 6);
+ CHECK(code_edit->get_line(0) == " test");
+ }
+
+ SUBCASE("[CodeEdit] unindent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Do nothing if not editable. */
+ code_edit->set_text("\t");
+
+ code_edit->set_editable(false);
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "\t");
+
+ code_edit->set_editable(true);
+
+ /* Simple unindent. */
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Should inindent inplace. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test\t");
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Backspace does a simple unindent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("\t");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Unindent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("\ttest");
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Caret on col zero unindent line. */
+ code_edit->set_text("\t\ttest");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Check input action. */
+ code_edit->set_text("\t\ttest");
+ SEND_GUI_ACTION(code_edit, "ui_text_dedent");
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Selection does entire line. */
+ code_edit->set_text("\t\ttest");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "\ttest");
+
+ /* Handles multiple lines. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Do not unindent line if last col is zero. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "\ttext");
+
+ /* Unindent even if last column of first line. */
+ code_edit->set_text("\ttest\n\ttext");
+ code_edit->select(0, 5, 1, 1);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text("\ttest");
+ code_edit->select(0, 1, 0, 2);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_selection_from_column() == 0);
+ CHECK(code_edit->get_selection_to_column() == 1);
+ CHECK(code_edit->get_line(0) == "test");
+ }
+
+ SUBCASE("[CodeEdit] unindent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Do nothing if not editable. */
+ code_edit->set_text(" ");
+
+ code_edit->set_editable(false);
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " ");
+
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == " ");
+
+ code_edit->set_editable(true);
+
+ /* Simple unindent. */
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Should inindent inplace. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test ");
+
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Backspace does a simple unindent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" ");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == "");
+
+ /* Backspace with letter. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" a");
+ code_edit->backspace();
+ CHECK(code_edit->get_line(0) == " ");
+
+ /* Unindent lines does entire line and works without selection. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret(" test");
+ code_edit->unindent_lines();
+ CHECK(code_edit->get_line(0) == "test");
+
+ /* Caret on col zero unindent line. */
+ code_edit->set_text(" test");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Only as far as needed */
+ code_edit->set_text(" test");
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Check input action. */
+ code_edit->set_text(" test");
+ SEND_GUI_ACTION(code_edit, "ui_text_dedent");
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Selection does entire line. */
+ code_edit->set_text(" test");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == " test");
+
+ /* Handles multiple lines. */
+ code_edit->set_text(" test\n text");
+ code_edit->select_all();
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Do not unindent line if last col is zero. */
+ code_edit->set_text(" test\n text");
+ code_edit->select(0, 0, 1, 0);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == " text");
+
+ /* Unindent even if last column of first line. */
+ code_edit->set_text(" test\n text");
+ code_edit->select(0, 5, 1, 1);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_line(0) == "test");
+ CHECK(code_edit->get_line(1) == "text");
+
+ /* Check selection is adjusted. */
+ code_edit->set_text(" test");
+ code_edit->select(0, 4, 0, 5);
+ code_edit->do_unindent();
+ CHECK(code_edit->get_selection_from_column() == 0);
+ CHECK(code_edit->get_selection_to_column() == 1);
+ CHECK(code_edit->get_line(0) == "test");
+ }
+
+ SUBCASE("[CodeEdit] auto indent") {
+ SUBCASE("[CodeEdit] auto indent tabs") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(false);
+
+ /* Simple indent on new line. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* new blank line should still indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* new line above should not indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test:");
+
+ /* Whitespace between symbol and caret is okay. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: ");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: ");
+ CHECK(code_edit->get_line(1) == "\t");
+
+ /* Comment between symbol and caret is okay. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # comment");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # comment");
+ CHECK(code_edit->get_line(1) == "\t");
+ code_edit->remove_comment_delimiter("#");
+
+ /* Strings between symbol and caret are not okay. */
+ code_edit->add_string_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # string");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # string");
+ CHECK(code_edit->get_line(1) == "");
+ code_edit->remove_comment_delimiter("#");
+
+ /* If between brace pairs an extra line is added. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test{");
+ CHECK(code_edit->get_line(1) == "\t");
+ CHECK(code_edit->get_line(2) == "}");
+
+ /* Except when we are going above. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test{}");
+
+ /* or below. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test{}");
+ CHECK(code_edit->get_line(1) == "");
+ }
+
+ SUBCASE("[CodeEdit] auto indent spaces") {
+ code_edit->set_indent_size(4);
+ code_edit->set_auto_indent_enabled(true);
+ code_edit->set_indent_using_spaces(true);
+
+ /* Simple indent on new line. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* new blank line should still indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test:");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* new line above should not indent. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test:");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test:");
+
+ /* Whitespace between symbol and caret is okay. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: ");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: ");
+ CHECK(code_edit->get_line(1) == " ");
+
+ /* Comment between symbol and caret is okay. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # comment");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # comment");
+ CHECK(code_edit->get_line(1) == " ");
+ code_edit->remove_comment_delimiter("#");
+
+ /* Strings between symbol and caret are not okay. */
+ code_edit->add_string_delimiter("#", "");
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test: # string");
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test: # string");
+ CHECK(code_edit->get_line(1) == "");
+ code_edit->remove_comment_delimiter("#");
+
+ /* If between brace pairs an extra line is added. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline");
+ CHECK(code_edit->get_line(0) == "test{");
+ CHECK(code_edit->get_line(1) == " ");
+ CHECK(code_edit->get_line(2) == "}");
+
+ /* Except when we are going above. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_above");
+ CHECK(code_edit->get_line(0) == "");
+ CHECK(code_edit->get_line(1) == "test{}");
+
+ /* or below. */
+ code_edit->set_text("");
+ code_edit->insert_text_at_caret("test{}");
+ code_edit->set_caret_column(5);
+ SEND_GUI_ACTION(code_edit, "ui_text_newline_blank");
+ CHECK(code_edit->get_line(0) == "test{}");
+ CHECK(code_edit->get_line(1) == "");
+ }
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] folding") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] folding settings") {
+ code_edit->set_line_folding_enabled(true);
+ CHECK(code_edit->is_line_folding_enabled());
+
+ code_edit->set_line_folding_enabled(false);
+ CHECK_FALSE(code_edit->is_line_folding_enabled());
+ }
+
+ SUBCASE("[CodeEdit] folding") {
+ code_edit->set_line_folding_enabled(true);
+
+ // No indent.
+ code_edit->set_text("line1\nline2\nline3");
+ for (int i = 0; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Indented lines.
+ code_edit->set_text("\tline1\n\tline2\n\tline3");
+ for (int i = 0; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Indent.
+ code_edit->set_text("line1\n\tline2\nline3");
+ CHECK(code_edit->can_fold_line(0));
+ for (int i = 1; i < 2; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 2);
+
+ // Nested indents.
+ code_edit->set_text("line1\n\tline2\n\t\tline3\nline4");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK(code_edit->can_fold_line(1));
+ for (int i = 2; i < 3; i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK_FALSE(code_edit->is_line_folded(3));
+ CHECK(code_edit->get_next_visible_line_offset_from(2, 1) == 2);
+
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK_FALSE(code_edit->is_line_folded(2));
+ CHECK_FALSE(code_edit->is_line_folded(3));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ // Check metadata.
+ CHECK(code_edit->get_folded_lines().size() == 1);
+ CHECK((int)code_edit->get_folded_lines()[0] == 0);
+
+ // Cannot unfold nested.
+ code_edit->unfold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // (un)Fold all / toggle.
+ code_edit->unfold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Check metadata.
+ CHECK(code_edit->get_folded_lines().size() == 0);
+
+ code_edit->fold_all_lines();
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ code_edit->unfold_all_lines();
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->toggle_foldable_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 3);
+
+ // Can also unfold from hidden line.
+ code_edit->unfold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Blank lines.
+ code_edit->set_text("line1\n\tline2\n\n\n\ttest\n\nline3");
+ CHECK(code_edit->can_fold_line(0));
+ for (int i = 1; i < code_edit->get_line_count(); i++) {
+ CHECK_FALSE(code_edit->can_fold_line(i));
+ code_edit->fold_line(i);
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ for (int i = 1; i < code_edit->get_line_count(); i++) {
+ CHECK_FALSE(code_edit->is_line_folded(i));
+ }
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 6);
+
+ // End of file.
+ code_edit->set_text("line1\n\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Comment & string blocks.
+ // Single line block
+ code_edit->add_comment_delimiter("#", "", true);
+ code_edit->set_text("#line1\n#\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test #line1\n#\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("#line1\ntest #\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // String.
+ code_edit->add_string_delimiter("^", "", true);
+ code_edit->set_text("^line1\n^\tline2");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test ^line1\n^\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("^line1\ntest ^\tline2");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Multiline blocks.
+ code_edit->add_comment_delimiter("&", "&", false);
+ code_edit->set_text("&line1\n\tline2&");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test &line1\n\tline2&");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("&line1\n\tline2& test");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Strings.
+ code_edit->add_string_delimiter("$", "$", false);
+ code_edit->set_text("$line1\n\tline2$");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Has to be full line.
+ code_edit->set_text("test $line1\n\tline2$");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ code_edit->set_text("$line1\n\tline2$ test");
+ CHECK_FALSE(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK_FALSE(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 1);
+
+ // Non-indented comments/ strings.
+ // Single line
+ code_edit->set_text("test\n\tline1\n#line1\n#line2\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ code_edit->set_text("test\n\tline1\n^line1\n^line2\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ // Multiline
+ code_edit->set_text("test\n\tline1\n&line1\nline2&\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+
+ code_edit->set_text("test\n\tline1\n$line1\nline2$\n\ttest");
+ CHECK(code_edit->can_fold_line(0));
+ CHECK_FALSE(code_edit->can_fold_line(1));
+ code_edit->fold_line(1);
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ code_edit->fold_line(0);
+ CHECK(code_edit->is_line_folded(0));
+ CHECK_FALSE(code_edit->is_line_folded(1));
+ CHECK(code_edit->get_next_visible_line_offset_from(1, 1) == 4);
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] completion") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ SUBCASE("[CodeEdit] auto brace completion") {
+ code_edit->set_auto_brace_completion_enabled(true);
+ CHECK(code_edit->is_auto_brace_completion_enabled());
+
+ code_edit->set_highlight_matching_braces_enabled(true);
+ CHECK(code_edit->is_highlight_matching_braces_enabled());
+
+ /* Try setters, any length. */
+ Dictionary auto_brace_completion_pairs;
+ auto_brace_completion_pairs["["] = "]";
+ auto_brace_completion_pairs["'"] = "'";
+ auto_brace_completion_pairs[";"] = "'";
+ auto_brace_completion_pairs["'''"] = "'''";
+ code_edit->set_auto_brace_completion_pairs(auto_brace_completion_pairs);
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+ CHECK(code_edit->get_auto_brace_completion_pairs()["["] == "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs()["'"] == "'");
+ CHECK(code_edit->get_auto_brace_completion_pairs()[";"] == "'");
+ CHECK(code_edit->get_auto_brace_completion_pairs()["'''"] == "'''");
+
+ ERR_PRINT_OFF;
+
+ /* No duplicate start keys. */
+ code_edit->add_auto_brace_completion_pair("[", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ /* No empty keys. */
+ code_edit->add_auto_brace_completion_pair("[", "");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("", "");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ /* Must be a symbol. */
+ code_edit->add_auto_brace_completion_pair("a", "]");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("[", "a");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ code_edit->add_auto_brace_completion_pair("a", "a");
+ CHECK(code_edit->get_auto_brace_completion_pairs().size() == 4);
+
+ ERR_PRINT_ON;
+
+ /* Check metadata. */
+ CHECK(code_edit->has_auto_brace_completion_open_key("["));
+ CHECK(code_edit->has_auto_brace_completion_open_key("'"));
+ CHECK(code_edit->has_auto_brace_completion_open_key(";"));
+ CHECK(code_edit->has_auto_brace_completion_open_key("'''"));
+ CHECK_FALSE(code_edit->has_auto_brace_completion_open_key("("));
+
+ CHECK(code_edit->has_auto_brace_completion_close_key("]"));
+ CHECK(code_edit->has_auto_brace_completion_close_key("'"));
+ CHECK(code_edit->has_auto_brace_completion_close_key("'''"));
+ CHECK_FALSE(code_edit->has_auto_brace_completion_close_key(")"));
+
+ CHECK(code_edit->get_auto_brace_completion_close_key("[") == "]");
+ CHECK(code_edit->get_auto_brace_completion_close_key("'") == "'");
+ CHECK(code_edit->get_auto_brace_completion_close_key(";") == "'");
+ CHECK(code_edit->get_auto_brace_completion_close_key("'''") == "'''");
+ CHECK(code_edit->get_auto_brace_completion_close_key("(").is_empty());
+
+ /* Check typing inserts closing pair. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETLEFT);
+ CHECK(code_edit->get_line(0) == "[]");
+
+ /* Should first match and insert smaller key. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "''");
+ CHECK(code_edit->get_caret_column() == 1);
+
+ /* Move out from centre, Should match and insert larger key. */
+ SEND_GUI_ACTION(code_edit, "ui_text_caret_right");
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "''''''");
+ CHECK(code_edit->get_caret_column() == 3);
+
+ /* Backspace should remove all. */
+ SEND_GUI_ACTION(code_edit, "ui_text_backspace");
+ CHECK(code_edit->get_line(0).is_empty());
+
+ /* If in between and typing close key should "skip". */
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETLEFT);
+ CHECK(code_edit->get_line(0) == "[]");
+ CHECK(code_edit->get_caret_column() == 1);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_BRACKETRIGHT);
+ CHECK(code_edit->get_line(0) == "[]");
+ CHECK(code_edit->get_caret_column() == 2);
+
+ /* If current is char and inserting a string, do not autocomplete. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_A);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "A'");
+
+ /* If in comment, do not complete. */
+ code_edit->add_comment_delimiter("#", "");
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_NUMBERSIGN);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ CHECK(code_edit->get_line(0) == "#'");
+
+ /* If in string, and inserting string do not complete. */
+ code_edit->clear();
+ SEND_GUI_KEY_EVENT(code_edit, KEY_APOSTROPHE);
+ SEND_GUI_KEY_EVENT(code_edit, KEY_QUOTEDBL);
+ CHECK(code_edit->get_line(0) == "'\"'");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete") {
+ code_edit->set_code_completion_enabled(true);
+ CHECK(code_edit->is_code_completion_enabled());
+
+ /* Set prefixes, single char only, disallow empty. */
+ TypedArray<String> completion_prefixes;
+ completion_prefixes.push_back("");
+ completion_prefixes.push_back(".");
+ completion_prefixes.push_back(".");
+ completion_prefixes.push_back(",,");
+
+ ERR_PRINT_OFF;
+ code_edit->set_code_completion_prefixes(completion_prefixes);
+ ERR_PRINT_ON;
+ completion_prefixes = code_edit->get_code_completion_prefixes();
+ CHECK(completion_prefixes.size() == 2);
+ CHECK(completion_prefixes.has("."));
+ CHECK(completion_prefixes.has(","));
+
+ code_edit->set_text("test\ntest");
+ CHECK(code_edit->get_text_for_code_completion() == String::chr(0xFFFF) + "test\ntest");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete request") {
+ SIGNAL_WATCH(code_edit, "request_code_completion");
+ code_edit->set_code_completion_enabled(true);
+
+ Array signal_args;
+ signal_args.push_back(Array());
+
+ /* Force request. */
+ code_edit->request_code_completion();
+ SIGNAL_CHECK_FALSE("request_code_completion");
+ code_edit->request_code_completion(true);
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Manual request should force. */
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_query");
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Insert prefix. */
+ TypedArray<String> completion_prefixes;
+ completion_prefixes.push_back(".");
+ code_edit->set_code_completion_prefixes(completion_prefixes);
+
+ code_edit->insert_text_at_caret(".");
+ code_edit->request_code_completion();
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Should work with space too. */
+ code_edit->insert_text_at_caret(" ");
+ code_edit->request_code_completion();
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ /* Should work when complete ends with prefix. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("t");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "test.", "test.");
+ code_edit->update_code_completion_options();
+ code_edit->confirm_code_completion();
+ CHECK(code_edit->get_line(0) == "test.");
+ SIGNAL_CHECK("request_code_completion", signal_args);
+
+ SIGNAL_UNWATCH(code_edit, "request_code_completion");
+ }
+
+ SUBCASE("[CodeEdit] autocomplete completion") {
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ code_edit->set_code_completion_enabled(true);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+
+ code_edit->update_code_completion_options();
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 0);
+ CHECK(code_edit->get_code_completion_options().size() == 0);
+
+ /* Adding does not update the list. */
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_0.", "item_0");
+
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 0);
+ CHECK(code_edit->get_code_completion_options().size() == 0);
+
+ /* After update, pending add should not be counted, */
+ /* also does not work on col 0 */
+ code_edit->insert_text_at_caret("i");
+ code_edit->update_code_completion_options();
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0", Color(1, 0, 0), RES(), Color(1, 0, 0));
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_1.", "item_1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "item_2.", "item_2");
+
+ ERR_PRINT_OFF;
+ code_edit->set_code_completion_selected_index(1);
+ ERR_PRINT_ON;
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+ CHECK(code_edit->get_code_completion_option(0).size() == 6);
+ CHECK(code_edit->get_code_completion_options().size() == 1);
+
+ /* Check cancel closes completion. */
+ SEND_GUI_ACTION(code_edit, "ui_cancel");
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+
+ code_edit->update_code_completion_options();
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+ code_edit->set_code_completion_selected_index(1);
+ CHECK(code_edit->get_code_completion_selected_index() == 1);
+ CHECK(code_edit->get_code_completion_option(0).size() == 6);
+ CHECK(code_edit->get_code_completion_options().size() == 3);
+
+ /* Check data. */
+ Dictionary option = code_edit->get_code_completion_option(0);
+ CHECK((int)option["kind"] == (int)CodeEdit::CodeCompletionKind::KIND_CLASS);
+ CHECK(option["display_text"] == "item_0.");
+ CHECK(option["insert_text"] == "item_0");
+ CHECK(option["font_color"] == Color(1, 0, 0));
+ CHECK(option["icon"] == RES());
+ CHECK(option["default_value"] == Color(1, 0, 0));
+
+ /* Set size for mouse input. */
+ code_edit->set_size(Size2(100, 100));
+
+ /* Check input. */
+ SEND_GUI_ACTION(code_edit, "ui_end");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_home");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_page_down");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_page_up");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_up");
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ SEND_GUI_ACTION(code_edit, "ui_down");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_KEY_EVENT(code_edit, KEY_T);
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_left");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_right");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ SEND_GUI_ACTION(code_edit, "ui_text_backspace");
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ Point2 caret_pos = code_edit->get_caret_draw_pos();
+ caret_pos.y -= code_edit->get_line_height();
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_WHEEL_DOWN, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_code_completion_selected_index() == 1);
+
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_code_completion_selected_index() == 0);
+
+ /* Single click selects. */
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MASK_LEFT);
+ CHECK(code_edit->get_code_completion_selected_index() == 2);
+
+ /* Double click inserts. */
+ SEND_GUI_DOUBLE_CLICK(code_edit, caret_pos);
+ CHECK(code_edit->get_code_completion_selected_index() == -1);
+ CHECK(code_edit->get_line(0) == "item_2");
+
+ code_edit->set_auto_brace_completion_enabled(false);
+
+ /* Does nothing in readonly. */
+ code_edit->undo();
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ code_edit->set_editable(false);
+ code_edit->confirm_code_completion();
+ code_edit->set_editable(true);
+ CHECK(code_edit->get_line(0) == "i");
+
+ /* Replace */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0 test");
+
+ /* Replace string. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test\"");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "\"item_0\"");
+
+ /* Normal replace if no end is given. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "\"item_0\" test");
+
+ /* Insert at completion. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_accept");
+ CHECK(code_edit->get_line(0) == "item_01 test");
+
+ /* Insert at completion with string should have same output. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("\"item_1 test\"");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0.", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_accept");
+ CHECK(code_edit->get_line(0) == "\"item_0\"1 test\"");
+
+ /* Merge symbol at end on insert text. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* Full set. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* Autobrace completion. */
+ code_edit->set_auto_brace_completion_enabled(true);
+
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1( test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0(", "item_0(");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0( test");
+ CHECK(code_edit->get_caret_column() == 7);
+
+ /* Full set. */
+ /* End on completion entry. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1 test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+
+ /* End of text*/
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0", "item_0");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 6);
+
+ /* End of both. */
+ code_edit->clear();
+ code_edit->insert_text_at_caret("item_1() test");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_CLASS, "item_0()", "item_0()");
+ code_edit->update_code_completion_options();
+ SEND_GUI_ACTION(code_edit, "ui_text_completion_replace");
+ CHECK(code_edit->get_line(0) == "item_0() test");
+ CHECK(code_edit->get_caret_column() == 8);
+ }
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] symbol lookup") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ code_edit->set_symbol_lookup_on_click_enabled(true);
+ CHECK(code_edit->is_symbol_lookup_on_click_enabled());
+
+ /* Set size for mouse input. */
+ code_edit->set_size(Size2(100, 100));
+
+ code_edit->set_text("this is some text");
+
+ Point2 caret_pos = code_edit->get_caret_draw_pos();
+ caret_pos.x += 55;
+ SEND_GUI_MOUSE_EVENT(code_edit, caret_pos, MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE);
+ CHECK(code_edit->get_text_for_symbol_lookup() == "this is s" + String::chr(0xFFFF) + "ome text");
+
+ SIGNAL_WATCH(code_edit, "symbol_validate");
+
+#ifdef OSX_ENABLED
+ SEND_GUI_KEY_EVENT(code_edit, KEY_META);
+#else
+ SEND_GUI_KEY_EVENT(code_edit, KEY_CTRL);
+#endif
+
+ Array signal_args;
+ Array arg;
+ arg.push_back("some");
+ signal_args.push_back(arg);
+ SIGNAL_CHECK("symbol_validate", signal_args);
+
+ SIGNAL_UNWATCH(code_edit, "symbol_validate");
+
+ memdelete(code_edit);
+}
+
+TEST_CASE("[SceneTree][CodeEdit] line length guidelines") {
+ CodeEdit *code_edit = memnew(CodeEdit);
+ SceneTree::get_singleton()->get_root()->add_child(code_edit);
+
+ TypedArray<int> guide_lines;
+
+ code_edit->set_line_length_guidelines(guide_lines);
+ CHECK(code_edit->get_line_length_guidelines().size() == 0);
+
+ guide_lines.push_back(80);
+ guide_lines.push_back(120);
+
+ /* Order should be preserved. */
+ code_edit->set_line_length_guidelines(guide_lines);
+ CHECK((int)code_edit->get_line_length_guidelines()[0] == 80);
+ CHECK((int)code_edit->get_line_length_guidelines()[1] == 120);
+
+ memdelete(code_edit);
+}
+
} // namespace TestCodeEdit
#endif // TEST_CODE_EDIT_H
diff --git a/tests/test_macros.h b/tests/test_macros.h
index bf1001fa67..2f0bc6dcfa 100644
--- a/tests/test_macros.h
+++ b/tests/test_macros.h
@@ -131,8 +131,12 @@ int register_test_command(String p_command, TestFunc p_function);
register_test_command(m_command, m_function); \
DOCTEST_GLOBAL_NO_WARNINGS_END()
-// Utility macro to send an action event to a given object
+// Utility macros to send an event actions to a given object
// Requires Message Queue and InputMap to be setup.
+// SEND_GUI_ACTION - takes an object and a input map key. e.g SEND_GUI_ACTION(code_edit, "ui_text_newline").
+// SEND_GUI_KEY_EVENT - takes an object and a keycode set. e.g SEND_GUI_KEY_EVENT(code_edit, KEY_A | KEY_MASK_CMD).
+// SEND_GUI_MOUSE_EVENT - takes an object, position, mouse button and mouse mask e.g SEND_GUI_MOUSE_EVENT(code_edit, Vector2(50, 50), MOUSE_BUTTON_NONE, MOUSE_BUTTON_NONE);
+// SEND_GUI_DOUBLE_CLICK - takes an object and a postion. e.g SEND_GUI_DOUBLE_CLICK(code_edit, Vector2(50, 50));
#define SEND_GUI_ACTION(m_object, m_action) \
{ \
@@ -144,6 +148,37 @@ int register_test_command(String p_command, TestFunc p_function);
MessageQueue::get_singleton()->flush(); \
}
+#define SEND_GUI_KEY_EVENT(m_object, m_input) \
+ { \
+ Ref<InputEventKey> event = InputEventKey::create_reference(m_input); \
+ event->set_pressed(true); \
+ m_object->gui_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
+#define _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask) \
+ Ref<InputEventMouseButton> event; \
+ event.instantiate(); \
+ event->set_position(m_local_pos); \
+ event->set_button_index(m_input); \
+ event->set_button_mask(m_mask); \
+ event->set_pressed(true);
+
+#define SEND_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask) \
+ { \
+ _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, m_input, m_mask); \
+ m_object->get_viewport()->push_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
+#define SEND_GUI_DOUBLE_CLICK(m_object, m_local_pos) \
+ { \
+ _CREATE_GUI_MOUSE_EVENT(m_object, m_local_pos, MOUSE_BUTTON_LEFT, MOUSE_BUTTON_LEFT); \
+ event->set_double_click(true); \
+ m_object->get_viewport()->push_input(event); \
+ MessageQueue::get_singleton()->flush(); \
+ }
+
// Utility class / macros for testing signals
//
// Use SIGNAL_WATCH(*object, "signal_name") to start watching