From 6c5fdd40492d8e10d3a6678afdaadcd8d3541d43 Mon Sep 17 00:00:00 2001 From: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Date: Tue, 28 May 2024 12:15:00 +0200 Subject: [PATCH] Add `String::replace_char(s)` methods for performance and convenience --- core/config/project_settings.cpp | 6 +- core/doc_data.cpp | 6 +- core/io/dir_access.cpp | 2 +- core/io/file_access.cpp | 2 +- core/io/file_access_pack.cpp | 2 +- core/io/image.cpp | 2 +- core/io/logger.cpp | 2 +- core/os/os.cpp | 2 +- core/string/translation_server.cpp | 2 +- core/string/ustring.cpp | 136 +++++++++++++++++- core/string/ustring.h | 3 + core/variant/variant_call.cpp | 2 + doc/classes/String.xml | 16 +++ doc/classes/StringName.xml | 16 +++ drivers/egl/egl_manager.cpp | 4 +- .../metal/rendering_device_driver_metal.mm | 2 +- drivers/unix/file_access_unix_pipe.cpp | 2 +- drivers/windows/dir_access_windows.cpp | 14 +- drivers/windows/file_access_windows.cpp | 6 +- drivers/windows/file_access_windows_pipe.cpp | 2 +- .../debug_adapter/debug_adapter_parser.cpp | 2 +- .../debug_adapter/debug_adapter_parser.h | 2 +- editor/doc_tools.cpp | 2 +- editor/editor_file_system.cpp | 4 +- editor/editor_help.cpp | 5 +- editor/editor_node.cpp | 12 +- editor/editor_resource_preview.cpp | 2 +- editor/export/editor_export_platform.cpp | 2 +- editor/export/project_export.cpp | 2 +- editor/export/project_zip_packer.cpp | 4 +- editor/gui/editor_file_dialog.cpp | 2 +- editor/gui/editor_spin_slider.cpp | 4 +- editor/import/3d/resource_importer_obj.cpp | 8 +- editor/plugins/font_config_plugin.cpp | 2 +- editor/plugins/plugin_config_dialog.cpp | 2 +- .../plugins/sprite_frames_editor_plugin.cpp | 2 +- editor/project_manager/project_dialog.cpp | 2 +- editor/property_selector.cpp | 2 +- main/main.cpp | 2 +- .../gdscript_language_protocol.cpp | 2 +- modules/gdscript/language_server/godot_lsp.h | 3 +- .../gdscript/tests/gdscript_test_runner.cpp | 2 +- modules/gltf/gltf_document.cpp | 7 +- modules/gltf/skin_tool.cpp | 3 +- .../audio_stream_interactive.cpp | 10 +- modules/mono/csharp_script.cpp | 2 +- modules/mono/editor/bindings_generator.cpp | 4 +- modules/mono/editor/hostfxr_resolver.cpp | 2 +- platform/android/export/export_plugin.cpp | 2 +- platform/android/java_class_wrapper.cpp | 6 +- platform/ios/os_ios.mm | 4 +- .../linuxbsd/freedesktop_portal_desktop.cpp | 2 +- platform/macos/os_macos.mm | 2 +- platform/web/os_web.cpp | 2 +- .../windows/crash_handler_windows_signal.cpp | 2 +- platform/windows/display_server_windows.cpp | 14 +- platform/windows/os_windows.cpp | 24 ++-- scene/gui/file_dialog.cpp | 2 +- scene/gui/spin_box.cpp | 4 +- scene/resources/3d/importer_mesh.cpp | 7 +- scene/resources/animation_library.cpp | 6 +- scene/resources/packed_scene.cpp | 2 +- servers/rendering/rendering_device.cpp | 2 +- tests/core/config/test_project_settings.h | 2 +- tests/core/string/test_string.h | 21 +++ 65 files changed, 301 insertions(+), 130 deletions(-) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index fdcd4f9b6064..2fdf1a470a99 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -173,7 +173,7 @@ String ProjectSettings::localize_path(const String &p_path) const { if (dir->change_dir(path) == OK) { String cwd = dir->get_current_dir(); - cwd = cwd.replace("\\", "/"); + cwd = cwd.replace_char('\\', '/'); // Ensure that we end with a '/'. // This is important to ensure that we do not wrongly localize the resource path @@ -582,7 +582,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b if (!OS::get_singleton()->get_resource_dir().is_empty()) { // OS will call ProjectSettings->get_resource_path which will be empty if not overridden! // If the OS would rather use a specific location, then it will not be empty. - resource_path = OS::get_singleton()->get_resource_dir().replace("\\", "/"); + resource_path = OS::get_singleton()->get_resource_dir().replace_char('\\', '/'); if (!resource_path.is_empty() && resource_path[resource_path.length() - 1] == '/') { resource_path = resource_path.substr(0, resource_path.length() - 1); // Chop end. } @@ -703,7 +703,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b while (true) { // Set the resource path early so things can be resolved when loading. resource_path = current_dir; - resource_path = resource_path.replace("\\", "/"); // Windows path to Unix path just in case. + resource_path = resource_path.replace_char('\\', '/'); // Windows path to Unix path just in case. err = _load_settings_text_or_binary(current_dir.path_join("project.godot"), current_dir.path_join("project.binary")); if (err == OK && !p_ignore_override) { // Optional, we don't mind if it fails. diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 2881443545f6..392d98eaeb57 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -32,11 +32,11 @@ String DocData::get_default_value_string(const Variant &p_value) { if (p_value.get_type() == Variant::ARRAY) { - return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace("\n", " "); + return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' '); } else if (p_value.get_type() == Variant::DICTIONARY) { - return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace("\n", " "); + return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' '); } else { - return p_value.get_construct_string().replace("\n", " "); + return p_value.get_construct_string().replace_char('\n', ' '); } } diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 4bdfc5a51cd5..6d621172a8fa 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -146,7 +146,7 @@ Error DirAccess::make_dir_recursive(const String &p_dir) { full_dir = p_dir; } - full_dir = full_dir.replace("\\", "/"); + full_dir = full_dir.replace_char('\\', '/'); String base; diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp index 230184ad04b6..fc7acae1c482 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -259,7 +259,7 @@ FileAccess::AccessType FileAccess::get_access_type() const { String FileAccess::fix_path(const String &p_path) const { // Helper used by file accesses that use a single filesystem. - String r_path = p_path.replace("\\", "/"); + String r_path = p_path.replace_char('\\', '/'); switch (_access_type) { case ACCESS_RESOURCES: { diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 3fba2c70679a..f563554e1b0b 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -550,7 +550,7 @@ String DirAccessPack::get_drive(int p_drive) { } PackedData::PackedDir *DirAccessPack::_find_dir(const String &p_dir) { - String nd = p_dir.replace("\\", "/"); + String nd = p_dir.replace_char('\\', '/'); // Special handling since simplify_path() will forbid it if (p_dir == "..") { diff --git a/core/io/image.cpp b/core/io/image.cpp index 45f9599cbe38..f83192781f5d 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2297,7 +2297,7 @@ void Image::initialize_data(const char **p_xpm) { switch (status) { case READING_HEADER: { String line_str = line_ptr; - line_str.replace("\t", " "); + line_str.replace_char('\t', ' '); size_width = line_str.get_slicec(' ', 0).to_int(); size_height = line_str.get_slicec(' ', 1).to_int(); diff --git a/core/io/logger.cpp b/core/io/logger.cpp index f6a61282044b..541554183aa4 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -153,7 +153,7 @@ void RotatedFileLogger::rotate_file() { if (FileAccess::exists(base_path)) { if (max_files > 1) { - String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace(":", "."); + String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace_char(':', '.'); String backup_name = base_path.get_basename() + timestamp; if (!base_path.get_extension().is_empty()) { backup_name += "." + base_path.get_extension(); diff --git a/core/os/os.cpp b/core/os/os.cpp index c161b2212f40..85b85aa6bb82 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -229,7 +229,7 @@ String OS::get_safe_dir_name(const String &p_dir_name, bool p_allow_paths) const if (p_allow_paths) { // Dir separators are allowed, but disallow ".." to avoid going up the filesystem invalid_chars.push_back(".."); - safe_dir_name = safe_dir_name.replace("\\", "/").strip_edges(); + safe_dir_name = safe_dir_name.replace_char('\\', '/').strip_edges(); } else { invalid_chars.push_back("/"); invalid_chars.push_back("\\"); diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index cb2bfad6df24..2af7712985be 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -135,7 +135,7 @@ TranslationServer::Locale::operator String() const { TranslationServer::Locale::Locale(const TranslationServer &p_server, const String &p_locale, bool p_add_defaults) { // Replaces '-' with '_' for macOS style locales. - String univ_locale = p_locale.replace("-", "_"); + String univ_locale = p_locale.replace_char('-', '_'); // Extract locale elements. Vector locale_elements = univ_locale.get_slice("@", 0).split("_"); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index c879cf990082..27338b66881d 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -982,7 +982,7 @@ String String::_camelcase_to_underscore() const { } String String::capitalize() const { - String aux = _camelcase_to_underscore().replace("_", " ").strip_edges(); + String aux = _camelcase_to_underscore().replace_char('_', ' ').strip_edges(); String cap; for (int i = 0; i < aux.get_slice_count(" "); i++) { String slice = aux.get_slicec(' ', i); @@ -1011,7 +1011,7 @@ String String::to_pascal_case() const { } String String::to_snake_case() const { - return _camelcase_to_underscore().replace(" ", "_").strip_edges(); + return _camelcase_to_underscore().replace_char(' ', '_').strip_edges(); } String String::get_with_code_lines() const { @@ -3074,6 +3074,17 @@ String String::erase(int p_pos, int p_chars) const { return left(p_pos) + substr(p_pos + p_chars); } +template +static bool _contains_char(char32_t p_c, const T *p_chars, int p_chars_len) { + for (int i = 0; i < p_chars_len; ++i) { + if (p_c == (char32_t)p_chars[i]) { + return true; + } + } + + return false; +} + String String::substr(int p_from, int p_chars) const { if (p_chars == -1) { p_chars = length() - p_from; @@ -4088,6 +4099,117 @@ String String::replace_first(const char *p_key, const char *p_with) const { return *this; } +String String::replace_char(char32_t p_key, char32_t p_with) const { + ERR_FAIL_COND_V_MSG(p_with == 0, *this, "`with` must not be the NUL character."); + + if (p_key == 0) { + return *this; + } + + int len = length(); + if (len == 0) { + return *this; + } + + int index = 0; + const char32_t *old_ptr = ptr(); + for (; index < len; ++index) { + if (old_ptr[index] == p_key) { + break; + } + } + + // If no occurrence of `key` was found, return this. + if (index == len) { + return *this; + } + + // If we found at least one occurrence of `key`, create new string. + String new_string; + new_string.resize(len + 1); + char32_t *new_ptr = new_string.ptrw(); + + // Copy part of input before `key`. + memcpy(new_ptr, old_ptr, index * sizeof(char32_t)); + + new_ptr[index] = p_with; + + // Copy or replace rest of input. + for (++index; index < len; ++index) { + if (old_ptr[index] == p_key) { + new_ptr[index] = p_with; + } else { + new_ptr[index] = old_ptr[index]; + } + } + + new_ptr[index] = _null; + + return new_string; +} + +template +static String _replace_chars_common(const String &p_this, const T *p_keys, int p_keys_len, char32_t p_with) { + ERR_FAIL_COND_V_MSG(p_with == 0, p_this, "`with` must not be the NUL character."); + + // Delegate if p_keys is a single element. + if (p_keys_len == 1) { + return p_this.replace_char(*p_keys, p_with); + } else if (p_keys_len == 0) { + return p_this; + } + + int len = p_this.length(); + if (len == 0) { + return p_this; + } + + int index = 0; + const char32_t *old_ptr = p_this.ptr(); + for (; index < len; ++index) { + if (_contains_char(old_ptr[index], p_keys, p_keys_len)) { + break; + } + } + + // If no occurrence of `keys` was found, return this. + if (index == len) { + return p_this; + } + + // If we found at least one occurrence of `keys`, create new string. + String new_string; + new_string.resize(len + 1); + char32_t *new_ptr = new_string.ptrw(); + + // Copy part of input before `key`. + memcpy(new_ptr, old_ptr, index * sizeof(char32_t)); + + new_ptr[index] = p_with; + + // Copy or replace rest of input. + for (++index; index < len; ++index) { + const char32_t old_char = old_ptr[index]; + if (_contains_char(old_char, p_keys, p_keys_len)) { + new_ptr[index] = p_with; + } else { + new_ptr[index] = old_char; + } + } + + new_ptr[index] = 0; + + return new_string; +} + +String String::replace_chars(const String &p_keys, char32_t p_with) const { + return _replace_chars_common(*this, p_keys.ptr(), p_keys.length(), p_with); +} + +String String::replace_chars(const char *p_keys, char32_t p_with) const { + return _replace_chars_common(*this, p_keys, strlen(p_keys), p_with); +} + String String::replacen(const String &p_key, const String &p_with) const { return _replace_common(*this, p_key, p_with, true); } @@ -4370,7 +4492,7 @@ String String::simplify_path() const { } } - s = s.replace("\\", "/"); + s = s.replace_char('\\', '/'); while (true) { // in case of using 2 or more slash String compare = s.replace("//", "/"); if (s == compare) { @@ -4986,8 +5108,8 @@ bool String::is_valid_float() const { String String::path_to_file(const String &p_path) const { // Don't get base dir for src, this is expected to be a dir already. - String src = replace("\\", "/"); - String dst = p_path.replace("\\", "/").get_base_dir(); + String src = replace_char('\\', '/'); + String dst = p_path.replace_char('\\', '/').get_base_dir(); String rel = src.path_to(dst); if (rel == dst) { // failed return p_path; @@ -4997,8 +5119,8 @@ String String::path_to_file(const String &p_path) const { } String String::path_to(const String &p_path) const { - String src = replace("\\", "/"); - String dst = p_path.replace("\\", "/"); + String src = replace_char('\\', '/'); + String dst = p_path.replace_char('\\', '/'); if (!src.ends_with("/")) { src += "/"; } diff --git a/core/string/ustring.h b/core/string/ustring.h index b8ebeb99d7cb..e57cb2825e83 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -429,6 +429,9 @@ class String { String replace_first(const char *p_key, const char *p_with) const; String replace(const String &p_key, const String &p_with) const; String replace(const char *p_key, const char *p_with) const; + String replace_char(char32_t p_key, char32_t p_with) const; + String replace_chars(const String &p_keys, char32_t p_with) const; + String replace_chars(const char *p_keys, char32_t p_with) const; String replacen(const String &p_key, const String &p_with) const; String replacen(const char *p_key, const char *p_with) const; String repeat(int p_count) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 5f6eb8399a27..95259b7922a2 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1713,6 +1713,8 @@ static void _register_variant_builtin_methods_string() { bind_string_method(format, sarray("values", "placeholder"), varray("{_}")); bind_string_methodv(replace, static_cast(&String::replace), sarray("what", "forwhat"), varray()); bind_string_methodv(replacen, static_cast(&String::replacen), sarray("what", "forwhat"), varray()); + bind_string_method(replace_char, sarray("key", "with"), varray()); + bind_string_methodv(replace_chars, static_cast(&String::replace_chars), sarray("keys", "with"), varray()); bind_string_method(repeat, sarray("count"), varray()); bind_string_method(reverse, sarray(), varray()); bind_string_method(insert, sarray("position", "what"), varray()); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 68a7f6733187..3235eef2dc72 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -778,6 +778,22 @@ Replaces all occurrences of [param what] inside the string with the given [param forwhat]. + + + + + + Replaces all occurrences of the Unicode character with code [param key] with the Unicode character with code [param with]. Faster version of [method replace] when the key is only one character long. To get a single character use [code]"X".unicode_at(0)[/code] (note that some strings, like compound letters and emoji, can be made up of multiple unicode codepoints, and will not work with this method, use [method length] to make sure). + + + + + + + + Replaces any occurrence of the characters in [param keys] with the Unicode character with code [param with]. See also [method replace_char]. + + diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 96972706e537..d299bb9d5a5f 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -686,6 +686,22 @@ Replaces all occurrences of [param what] inside the string with the given [param forwhat]. + + + + + + Replaces all occurrences of the Unicode character with code [param key] with the Unicode character with code [param with]. Faster version of [method replace] when the key is only one character long. To get a single character use [code]"X".unicode_at(0)[/code] (note that some strings, like compound letters and emoji, can be made up of multiple unicode codepoints, and will not work with this method, use [method length] to make sure). + + + + + + + + Replaces any occurrence of the characters in [param keys] with the Unicode character with code [param with]. See also [method replace_char]. + + diff --git a/drivers/egl/egl_manager.cpp b/drivers/egl/egl_manager.cpp index c545de39a630..5f7aca41cf7f 100644 --- a/drivers/egl/egl_manager.cpp +++ b/drivers/egl/egl_manager.cpp @@ -151,7 +151,7 @@ int EGLManager::_get_gldisplay_id(void *p_display) { String EGLManager::shader_cache_dir; void EGLManager::_set_cache(const void *p_key, EGLsizeiANDROID p_key_size, const void *p_value, EGLsizeiANDROID p_value_size) { - String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace("/", "_"); + String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace_char('/', '_'); String path = shader_cache_dir.path_join(name) + ".cache"; Error err = OK; @@ -163,7 +163,7 @@ void EGLManager::_set_cache(const void *p_key, EGLsizeiANDROID p_key_size, const } EGLsizeiANDROID EGLManager::_get_cache(const void *p_key, EGLsizeiANDROID p_key_size, void *p_value, EGLsizeiANDROID p_value_size) { - String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace("/", "_"); + String name = CryptoCore::b64_encode_str((const uint8_t *)p_key, p_key_size).replace_char('/', '_'); String path = shader_cache_dir.path_join(name) + ".cache"; Error err = OK; diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm index 2e32e99c2ebb..8743af4b9ceb 100644 --- a/drivers/metal/rendering_device_driver_metal.mm +++ b/drivers/metal/rendering_device_driver_metal.mm @@ -3044,7 +3044,7 @@ bool isArrayTexture(MTLTextureType p_type) { String RenderingDeviceDriverMetal::_pipeline_get_cache_path() const { String path = OS::get_singleton()->get_user_data_dir() + "/metal/pipelines"; - path += "." + context_device.name.validate_filename().replace(" ", "_").to_lower(); + path += "." + context_device.name.validate_filename().replace_char(' ', '_').to_lower(); if (Engine::get_singleton()->is_editor_hint()) { path += ".editor"; } diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp index 775d3f7d8676..a417535880a4 100644 --- a/drivers/unix/file_access_unix_pipe.cpp +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -66,7 +66,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) path_src = p_path; ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use."); - path = String("/tmp/") + p_path.replace("pipe://", "").replace("/", "_"); + path = String("/tmp/") + p_path.replace("pipe://", "").replace_char('/', '_'); const CharString path_utf8 = path.utf8(); struct stat st = {}; diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 3ddbde72c40b..0f0493e7badd 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -70,17 +70,17 @@ struct DirAccessWindowsPrivate { }; String DirAccessWindows::fix_path(const String &p_path) const { - String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/")); + String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace_char('\\', '/')); if (r_path.ends_with(":")) { r_path += "/"; } if (r_path.is_relative_path()) { - r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path); + r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path); } else if (r_path == ".") { - r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/"); + r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/'); } r_path = r_path.simplify_path(); - r_path = r_path.replace("/", "\\"); + r_path = r_path.replace_char('/', '\\'); if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) { r_path = R"(\\?\)" + r_path; } @@ -167,7 +167,7 @@ Error DirAccessWindows::change_dir(String p_dir) { str_len = GetCurrentDirectoryW(0, nullptr); real_current_dir_name.resize(str_len + 1); GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw()); - String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/"); + String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/'); if (!new_dir.begins_with(base)) { worked = false; } @@ -215,7 +215,7 @@ Error DirAccessWindows::make_dir(String p_dir) { } String DirAccessWindows::get_current_dir(bool p_include_drive) const { - String cdir = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/"); + String cdir = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/'); String base = _get_root_path(); if (!base.is_empty()) { String bd = cdir.replace_first(base, ""); @@ -419,7 +419,7 @@ String DirAccessWindows::read_link(String p_file) { GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED); CloseHandle(hfile); - return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace("\\", "/"); + return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace_char('\\', '/'); } Error DirAccessWindows::create_link(String p_source, String p_target) { diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index b2796e4e67b7..888b7af96471 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -87,10 +87,10 @@ String FileAccessWindows::fix_path(const String &p_path) const { size_t str_len = GetCurrentDirectoryW(0, nullptr); current_dir_name.resize(str_len + 1); GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); - r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path); + r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path); } r_path = r_path.simplify_path(); - r_path = r_path.replace("/", "\\"); + r_path = r_path.replace_char('/', '\\'); if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) { r_path = R"(\\?\)" + r_path; } @@ -282,7 +282,7 @@ String FileAccessWindows::get_path() const { } String FileAccessWindows::get_path_absolute() const { - return path.trim_prefix(R"(\\?\)").replace("\\", "/"); + return path.trim_prefix(R"(\\?\)").replace_char('\\', '/'); } bool FileAccessWindows::is_open() const { diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp index 88e76f67ef50..76903400beb0 100644 --- a/drivers/windows/file_access_windows_pipe.cpp +++ b/drivers/windows/file_access_windows_pipe.cpp @@ -60,7 +60,7 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag path_src = p_path; ERR_FAIL_COND_V_MSG(fd[0] != nullptr || fd[1] != nullptr, ERR_ALREADY_IN_USE, "Pipe is already in use."); - path = String("\\\\.\\pipe\\LOCAL\\") + p_path.replace("pipe://", "").replace("/", "_"); + path = String("\\\\.\\pipe\\LOCAL\\") + p_path.replace("pipe://", "").replace_char('/', '_'); HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (h == INVALID_HANDLE_VALUE) { diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.cpp b/editor/debugger/debug_adapter/debug_adapter_parser.cpp index 30abbbce8eb5..53b1a20d2061 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.cpp +++ b/editor/debugger/debug_adapter/debug_adapter_parser.cpp @@ -360,7 +360,7 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) co // If path contains \, it's a Windows path, so we need to convert it to /, and make the drive letter uppercase if (source.path.contains_char('\\')) { - source.path = source.path.replace("\\", "/"); + source.path = source.path.replace_char('\\', '/'); source.path = source.path.substr(0, 1).to_upper() + source.path.substr(1); } diff --git a/editor/debugger/debug_adapter/debug_adapter_parser.h b/editor/debugger/debug_adapter/debug_adapter_parser.h index 61cd6c7f9419..9a8091828cc3 100644 --- a/editor/debugger/debug_adapter/debug_adapter_parser.h +++ b/editor/debugger/debug_adapter/debug_adapter_parser.h @@ -49,7 +49,7 @@ class DebugAdapterParser : public Object { // If path contains \, it's a Windows path, so we need to convert it to /, and check as case-insensitive. if (p_path.contains_char('\\')) { String project_path = ProjectSettings::get_singleton()->get_resource_path(); - String path = p_path.replace("\\", "/"); + String path = p_path.replace_char('\\', '/'); return path.containsn(project_path); } return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path()); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 02babe6a068d..4b234e7fbccf 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -933,7 +933,7 @@ void DocTools::generate(BitField p_flags) { DocData::ConstantDoc constant; constant.name = E; Variant value = Variant::get_constant_value(Variant::Type(i), E); - constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace("\n", " "); + constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace_char('\n', ' '); constant.is_value_valid = true; constant.type = Variant::get_type_name(value.get_type()); c.constants.push_back(constant); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index b8ce3ff65bf7..68d2cd8a0a72 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -1796,7 +1796,7 @@ bool EditorFileSystem::_find_file(const String &p_file, EditorFileSystemDirector return false; } f = f.substr(6, f.length()); - f = f.replace("\\", "/"); + f = f.replace_char('\\', '/'); Vector path = f.split("/"); @@ -1912,7 +1912,7 @@ EditorFileSystemDirectory *EditorFileSystem::get_filesystem_path(const String &p } f = f.substr(6, f.length()); - f = f.replace("\\", "/"); + f = f.replace_char('\\', '/'); if (f.is_empty()) { return filesystem; } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 0caabffe56f6..20086d6179dd 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -102,7 +102,8 @@ const Vector classes_with_csharp_differences = { }; #endif -static const String nbsp = String::chr(160); +static const char32_t nbsp_chr = 160; +static const String nbsp = String::chr(nbsp_chr); static const String nbsp_equal_nbsp = nbsp + "=" + nbsp; static const String colon_nbsp = ":" + nbsp; @@ -120,7 +121,7 @@ const Vector packed_array_types = { }; static String _replace_nbsp_with_space(const String &p_string) { - return p_string.replace(nbsp, " "); + return p_string.replace_char(nbsp_chr, ' '); } static String _fix_constant(const String &p_constant) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4b7abdf2b072..83b10e9e985b 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -3221,11 +3221,11 @@ String EditorNode::adjust_scene_name_casing(const String &p_root_name) { // Use casing of the root node. break; case SCENE_NAME_CASING_PASCAL_CASE: - return p_root_name.replace("-", "_").to_pascal_case(); + return p_root_name.replace_char('-', '_').to_pascal_case(); case SCENE_NAME_CASING_SNAKE_CASE: - return p_root_name.replace("-", "_").to_snake_case(); + return p_root_name.replace_char('-', '_').to_snake_case(); case SCENE_NAME_CASING_KEBAB_CASE: - return p_root_name.to_snake_case().replace("_", "-"); + return p_root_name.to_snake_case().replace_char('_', '-'); } return p_root_name; } @@ -3242,11 +3242,11 @@ String EditorNode::adjust_script_name_casing(const String &p_file_name, ScriptLa // Script language has no preference, so do not adjust. break; case ScriptLanguage::SCRIPT_NAME_CASING_PASCAL_CASE: - return p_file_name.replace("-", "_").to_pascal_case(); + return p_file_name.replace_char('-', '_').to_pascal_case(); case ScriptLanguage::SCRIPT_NAME_CASING_SNAKE_CASE: - return p_file_name.replace("-", "_").to_snake_case(); + return p_file_name.replace_char('-', '_').to_snake_case(); case ScriptLanguage::SCRIPT_NAME_CASING_KEBAB_CASE: - return p_file_name.to_snake_case().replace("_", "-"); + return p_file_name.to_snake_case().replace_char('_', '-'); } return p_file_name; } diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index f53f2744a225..f1c9eb612c67 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -387,7 +387,7 @@ void EditorResourcePreview::_write_preview_cache(Ref p_file, int p_t p_file->store_line(itos(p_has_small_texture)); p_file->store_line(itos(p_modified_time)); p_file->store_line(p_hash); - p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace("\n", " ")); + p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace_char('\n', ' ')); p_file->store_line(itos(CURRENT_METADATA_VERSION)); } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index b931e6feba29..c1c36530ab61 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -456,7 +456,7 @@ void EditorExportPlatform::_export_find_dependencies(const String &p_path, HashS void EditorExportPlatform::_edit_files_with_filter(Ref &da, const Vector &p_filters, HashSet &r_list, bool exclude) { da->list_dir_begin(); - String cur_dir = da->get_current_dir().replace("\\", "/"); + String cur_dir = da->get_current_dir().replace_char('\\', '/'); if (!cur_dir.ends_with("/")) { cur_dir += "/"; } diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 5b93a1a17ec7..380c99e5c291 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -73,7 +73,7 @@ void ProjectExportTextureFormatError::_notification(int p_what) { } void ProjectExportTextureFormatError::show_for_texture_format(const String &p_friendly_name, const String &p_setting_identifier) { - texture_format_error_label->set_text(vformat(TTR("Target platform requires '%s' texture compression. Enable 'Import %s' to fix."), p_friendly_name, p_friendly_name.replace("/", " "))); + texture_format_error_label->set_text(vformat(TTR("Target platform requires '%s' texture compression. Enable 'Import %s' to fix."), p_friendly_name, p_friendly_name.replace_char('/', ' '))); setting_identifier = p_setting_identifier; show(); } diff --git a/editor/export/project_zip_packer.cpp b/editor/export/project_zip_packer.cpp index 2595bb016ba1..f5d978a10122 100644 --- a/editor/export/project_zip_packer.cpp +++ b/editor/export/project_zip_packer.cpp @@ -42,9 +42,9 @@ String ProjectZIPPacker::get_project_zip_safe_name() { // In the project name, all invalid characters become an empty string so that a name // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". const String project_name = GLOBAL_GET("application/config/name"); - const String project_name_safe = project_name.to_lower().replace(" ", "_"); + const String project_name_safe = project_name.to_lower().replace_char(' ', '_'); const String datetime_safe = - Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); + Time::get_singleton()->get_datetime_string_from_system(false, true).replace_char(' ', '_'); const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip", project_name_safe, datetime_safe)); return output_name; } diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 0f4d1161d745..704f7853a943 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -556,7 +556,7 @@ void EditorFileDialog::_action_pressed() { } else if (mode == FILE_MODE_OPEN_ANY || mode == FILE_MODE_OPEN_DIR) { String path = dir_access->get_current_dir(); - path = path.replace("\\", "/"); + path = path.replace_char('\\', '/'); for (int i = 0; i < item_list->get_item_count(); i++) { if (item_list->is_selected(i)) { diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index cef23fdda1f1..6cbc55c0015d 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -553,8 +553,8 @@ void EditorSpinSlider::_evaluate_input_text() { expr.instantiate(); // Convert commas ',' to dots '.' for French/German etc. keyboard layouts. - String text = value_input->get_text().replace(",", "."); - text = text.replace(";", ","); + String text = value_input->get_text().replace_char(',', '.'); + text = text.replace_char(';', ','); text = TS->parse_number(text); Error err = expr->parse(text); diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index efada9f2ecae..bc32edde8a4a 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -119,7 +119,7 @@ static Error _parse_material_library(const String &p_path, HashMap texture = ResourceLoader::load(path); diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp index 7cf0b2d2accd..4bb3204333bc 100644 --- a/editor/plugins/font_config_plugin.cpp +++ b/editor/plugins/font_config_plugin.cpp @@ -476,7 +476,7 @@ void EditorPropertyOTVariation::update_property() { String name = TS->tag_to_name(name_tag); String name_cap; { - String aux = name.replace("_", " ").strip_edges(); + String aux = name.replace_char('_', ' ').strip_edges(); for (int j = 0; j < aux.get_slice_count(" "); j++) { String slice = aux.get_slicec(' ', j); if (slice.length() > 0) { diff --git a/editor/plugins/plugin_config_dialog.cpp b/editor/plugins/plugin_config_dialog.cpp index c3e87c508edc..64092b0ead3d 100644 --- a/editor/plugins/plugin_config_dialog.cpp +++ b/editor/plugins/plugin_config_dialog.cpp @@ -144,7 +144,7 @@ void PluginConfigDialog::_on_required_text_changed() { } String PluginConfigDialog::_get_subfolder() { - return subfolder_edit->get_text().is_empty() ? name_edit->get_text().replace(" ", "_").to_lower() : subfolder_edit->get_text(); + return subfolder_edit->get_text().is_empty() ? name_edit->get_text().replace_char(' ', '_').to_lower() : subfolder_edit->get_text(); } String PluginConfigDialog::_to_absolute_plugin_path(const String &p_plugin_name) { diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 5cea634e6df8..9c92d4a6a80c 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1099,7 +1099,7 @@ void SpriteFramesEditor::_animation_name_edited() { new_name = "new_animation"; } - new_name = new_name.replace("/", "_").replace(",", " "); + new_name = new_name.replace_char('/', '_').replace_char(',', ' '); String name = new_name; int counter = 0; diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index 6f2e9da3e0c7..df890150ed76 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -277,7 +277,7 @@ void ProjectDialog::_update_target_auto_dir() { case 0: // No convention break; case 1: // kebab-case - new_auto_dir = new_auto_dir.to_lower().replace(" ", "-"); + new_auto_dir = new_auto_dir.to_lower().replace_char(' ', '-'); break; case 2: // snake_case new_auto_dir = new_auto_dir.to_snake_case(); diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index f0e9efe84de2..f7854747654e 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -81,7 +81,7 @@ void PropertySelector::_update_search() { TreeItem *root = search_options->create_item(); // Allow using spaces in place of underscores in the search string (makes the search more fault-tolerant). - const String search_text = search_box->get_text().replace(" ", "_"); + const String search_text = search_box->get_text().replace_char(' ', '_'); if (properties) { List props; diff --git a/main/main.cpp b/main/main.cpp index c65e6ba8acb4..f7ea3c14231f 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4182,7 +4182,7 @@ int Main::start() { String local_game_path; if (!game_path.is_empty() && !project_manager) { - local_game_path = game_path.replace("\\", "/"); + local_game_path = game_path.replace_char('\\', '/'); if (!local_game_path.begins_with("res://")) { bool absolute = diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index b636dbe580da..5926225ee982 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -179,7 +179,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { #ifndef WINDOWS_ENABLED is_same_workspace = root.to_lower() == workspace->root.to_lower(); #else - is_same_workspace = root.replace("\\", "/").to_lower() == workspace->root.to_lower(); + is_same_workspace = root.replace_char('\\', '/').to_lower() == workspace->root.to_lower(); #endif if (root_uri.length() && is_same_workspace) { diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 6e19cd7a233c..881c00c70ce9 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -1940,8 +1940,7 @@ static String marked_documentation(const String &p_bbcode) { line = line.replace("[signal ", "`"); line = line.replace("[enum ", "`"); line = line.replace("[constant ", "`"); - line = line.replace("[", "`"); - line = line.replace("]", "`"); + line = line.replace_chars("[]", '`'); } if (!in_code_block && i < lines.size() - 1) { diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 14dd79da9048..bf572102ba8d 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -476,7 +476,7 @@ void GDScriptTest::error_handler(void *p_this, const char *p_function, const cha if (include_source_info) { header += vformat(" at %s:%d on %s()", - String::utf8(p_file).trim_prefix(self->base_dir).replace("\\", "/"), + String::utf8(p_file).trim_prefix(self->base_dir).replace_char('\\', '/'), p_line, String::utf8(p_function)); } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index dd27fef7ad00..987b6817b9bd 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -525,8 +525,7 @@ String GLTFDocument::_gen_unique_animation_name(Ref p_state, const St String GLTFDocument::_sanitize_bone_name(const String &p_name) { String bone_name = p_name; - bone_name = bone_name.replace(":", "_"); - bone_name = bone_name.replace("/", "_"); + bone_name = bone_name.replace_chars(":/", '_'); return bone_name; } @@ -807,7 +806,7 @@ Error GLTFDocument::_parse_buffers(Ref p_state, const String &p_base_ } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace_char('\\', '/'); // Fix for Windows. ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri); buffer_data = FileAccess::get_file_as_bytes(uri); ERR_FAIL_COND_V_MSG(buffer_data.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); @@ -4107,7 +4106,7 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace_char('\\', '/'); // Fix for Windows. resource_uri = uri.simplify_path(); // ResourceLoader will rely on the file extension to use the relevant loader. // The spec says that if mimeType is defined, it should take precedence (e.g. diff --git a/modules/gltf/skin_tool.cpp b/modules/gltf/skin_tool.cpp index 1522c0e324d5..7b23e1369394 100644 --- a/modules/gltf/skin_tool.cpp +++ b/modules/gltf/skin_tool.cpp @@ -810,7 +810,6 @@ Error SkinTool::_asset_parse_skins( String SkinTool::_sanitize_bone_name(const String &p_name) { String bone_name = p_name; - bone_name = bone_name.replace(":", "_"); - bone_name = bone_name.replace("/", "_"); + bone_name = bone_name.replace_chars(":/", '_'); return bone_name; } diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index d4d620b0b140..1fd8238b7c2e 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -123,9 +123,9 @@ void AudioStreamInteractive::set_clip_stream(int p_clip, const Ref if (clips[p_clip].name == StringName() && p_stream.is_valid()) { String n; if (!clips[p_clip].stream->get_name().is_empty()) { - n = clips[p_clip].stream->get_name().replace(",", " "); + n = clips[p_clip].stream->get_name().replace_char(',', ' '); } else if (clips[p_clip].stream->get_path().is_resource_file()) { - n = clips[p_clip].stream->get_path().get_file().get_basename().replace(",", " "); + n = clips[p_clip].stream->get_path().get_file().get_basename().replace_char(',', ' '); n = n.capitalize(); } @@ -397,13 +397,13 @@ String AudioStreamInteractive::_get_streams_hint() const { if (i > 0) { stream_name_cache += ","; } - String n = String(clips[i].name).replace(",", " "); + String n = String(clips[i].name).replace_char(',', ' '); if (n == "" && clips[i].stream.is_valid()) { if (!clips[i].stream->get_name().is_empty()) { - n = clips[i].stream->get_name().replace(",", " "); + n = clips[i].stream->get_name().replace_char(',', ' '); } else if (clips[i].stream->get_path().is_resource_file()) { - n = clips[i].stream->get_path().get_file().replace(",", " "); + n = clips[i].stream->get_path().get_file().replace_char(',', ' '); } } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index e9a821c65a95..f7f3b699b0e2 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -364,7 +364,7 @@ Ref