diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index ed3d4b0b543..d8b93998afb 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -970,8 +970,42 @@ const char32_t *String::get_data() const {
return size() ? &operator[](0) : &zero;
}
+String String::_camelcase_to_underscore() const {
+ const char32_t *cstr = get_data();
+ String new_string;
+ int start_index = 0;
+
+ for (int i = 1; i < this->size(); i++) {
+ bool is_prev_upper = is_ascii_upper_case(cstr[i - 1]);
+ bool is_prev_lower = is_ascii_lower_case(cstr[i - 1]);
+ bool is_prev_digit = is_digit(cstr[i - 1]);
+
+ bool is_curr_upper = is_ascii_upper_case(cstr[i]);
+ bool is_curr_lower = is_ascii_lower_case(cstr[i]);
+ bool is_curr_digit = is_digit(cstr[i]);
+
+ bool is_next_lower = false;
+ if (i + 1 < this->size()) {
+ is_next_lower = is_ascii_lower_case(cstr[i + 1]);
+ }
+
+ const bool cond_a = is_prev_lower && is_curr_upper; // aA
+ const bool cond_b = (is_prev_upper || is_prev_digit) && is_curr_upper && is_next_lower; // AAa, 2Aa
+ const bool cond_c = is_prev_digit && is_curr_lower && is_next_lower; // 2aa
+ const bool cond_d = (is_prev_upper || is_prev_lower) && is_curr_digit; // A2, a2
+
+ if (cond_a || cond_b || cond_c || cond_d) {
+ new_string += this->substr(start_index, i - start_index) + "_";
+ start_index = i;
+ }
+ }
+
+ new_string += this->substr(start_index, this->size() - start_index);
+ return new_string.to_lower();
+}
+
String String::capitalize() const {
- String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges();
+ String aux = this->_camelcase_to_underscore().replace("_", " ").strip_edges();
String cap;
for (int i = 0; i < aux.get_slice_count(" "); i++) {
String slice = aux.get_slicec(' ', i);
@@ -987,45 +1021,20 @@ String String::capitalize() const {
return cap;
}
-String String::camelcase_to_underscore(bool lowercase) const {
- const char32_t *cstr = get_data();
- String new_string;
- int start_index = 0;
-
- for (int i = 1; i < this->size(); i++) {
- bool is_upper = is_ascii_upper_case(cstr[i]);
- bool is_number = is_digit(cstr[i]);
-
- bool are_next_2_lower = false;
- bool is_next_lower = false;
- bool is_next_number = false;
- bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]);
- bool was_precedent_number = is_digit(cstr[i - 1]);
-
- if (i + 2 < this->size()) {
- are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
- }
-
- if (i + 1 < this->size()) {
- is_next_lower = is_ascii_lower_case(cstr[i + 1]);
- is_next_number = is_digit(cstr[i + 1]);
- }
-
- const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number;
- const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower;
- const bool cond_c = is_number && !was_precedent_number;
- const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower;
- const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number);
-
- bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number;
- if (should_split) {
- new_string += this->substr(start_index, i - start_index) + "_";
- start_index = i;
- }
+String String::to_camel_case() const {
+ String s = this->to_pascal_case();
+ if (!s.is_empty()) {
+ s[0] = _find_lower(s[0]);
}
+ return s;
+}
- new_string += this->substr(start_index, this->size() - start_index);
- return lowercase ? new_string.to_lower() : new_string;
+String String::to_pascal_case() const {
+ return this->capitalize().replace(" ", "");
+}
+
+String String::to_snake_case() const {
+ return this->_camelcase_to_underscore().replace(" ", "_").strip_edges();
}
String String::get_with_code_lines() const {
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 2463fc35f7e..31de7cc4643 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -196,6 +196,7 @@ class String {
bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;
int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const;
+ String _camelcase_to_underscore() const;
public:
enum {
@@ -335,7 +336,9 @@ public:
static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
String capitalize() const;
- String camelcase_to_underscore(bool lowercase = true) const;
+ String to_camel_case() const;
+ String to_pascal_case() const;
+ String to_snake_case() const;
String get_with_code_lines() const;
int get_slice_count(String p_splitter) const;
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 8e73c8dfc0f..8af2a091115 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1506,6 +1506,9 @@ static void _register_variant_builtin_methods() {
bind_method(String, repeat, sarray("count"), varray());
bind_method(String, insert, sarray("position", "what"), varray());
bind_method(String, capitalize, sarray(), varray());
+ bind_method(String, to_camel_case, sarray(), varray());
+ bind_method(String, to_pascal_case, sarray(), varray());
+ bind_method(String, to_snake_case, sarray(), varray());
bind_method(String, split, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));
bind_method(String, rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));
bind_method(String, split_floats, sarray("delimiter", "allow_empty"), varray(true));
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index c111528c064..316bb923b79 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -775,6 +775,12 @@
Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces.
+
+
+
+ Returns the string converted to [code]camelCase[/code].
+
+
@@ -804,6 +810,18 @@
Returns the string converted to lowercase.
+
+
+
+ Returns the string converted to [code]PascalCase[/code].
+
+
+
+
+
+ Returns the string converted to [code]snake_case[/code].
+
+
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 587c16c229d..4a477cd0a14 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -753,22 +753,12 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
}
Dictionary subst;
-
- String s = node_name.capitalize().replace(" ", "");
- subst["NodeName"] = s;
- if (!s.is_empty()) {
- s[0] = s.to_lower()[0];
- }
- subst["nodeName"] = s;
- subst["node_name"] = node_name.capitalize().replace(" ", "_").to_lower();
-
- s = signal_name.capitalize().replace(" ", "");
- subst["SignalName"] = s;
- if (!s.is_empty()) {
- s[0] = s.to_lower()[0];
- }
- subst["signalName"] = s;
- subst["signal_name"] = signal_name.capitalize().replace(" ", "_").to_lower();
+ subst["NodeName"] = node_name.to_pascal_case();
+ subst["nodeName"] = node_name.to_camel_case();
+ subst["node_name"] = node_name.to_snake_case();
+ subst["SignalName"] = signal_name.to_pascal_case();
+ subst["signalName"] = signal_name.to_camel_case();
+ subst["signal_name"] = signal_name.to_snake_case();
String dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_name")).format(subst);
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 6d446546171..544b6c7141e 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -164,7 +164,7 @@ void EditorAutoloadSettings::_autoload_add() {
if (!fpath.ends_with("/")) {
fpath = fpath.get_base_dir();
}
- dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().camelcase_to_underscore())), false, false);
+ dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().to_snake_case())), false, false);
dialog->popup_centered();
} else {
if (autoload_add(autoload_add_name->get_text(), autoload_add_path->get_text())) {
@@ -371,7 +371,7 @@ void EditorAutoloadSettings::_autoload_open(const String &fpath) {
void EditorAutoloadSettings::_autoload_file_callback(const String &p_path) {
// Convert the file name to PascalCase, which is the convention for classes in GDScript.
- const String class_name = p_path.get_file().get_basename().capitalize().replace(" ", "");
+ const String class_name = p_path.get_file().get_basename().to_pascal_case();
// If the name collides with a built-in class, prefix the name to make it possible to add without having to edit the name.
// The prefix is subjective, but it provides better UX than leaving the Add button disabled :)
@@ -580,7 +580,7 @@ void EditorAutoloadSettings::_script_created(Ref