From 39053925968344fd8a2590474842a8d5adcd8ec9 Mon Sep 17 00:00:00 2001 From: Cyril Bissey <53737317+Cykyrios@users.noreply.github.com> Date: Thu, 13 Nov 2025 08:16:41 +0100 Subject: [PATCH] Add string placeholder syntax highlighting --- doc/classes/EditorSettings.xml | 4 ++ editor/settings/editor_settings.cpp | 2 + editor/themes/editor_theme_manager.cpp | 1 + .../gdscript/editor/gdscript_highlighter.cpp | 58 +++++++++++++++++-- .../gdscript/editor/gdscript_highlighter.h | 1 + 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 47584dea959..bdf99c849ce 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -1646,6 +1646,10 @@ The script editor's color for strings (single-line and multi-line). + + The script editor's color for string placeholders, such as [code]%s[/code] and [code]{_}[/code]. Refer to the [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html]GDScript format strings documentation[/url] for more details. + [b]Note:[/b] Only the default [code]{_}[/code] placeholder patterns are highlighted for the [method String.format] method. Custom patterns still appear as plain strings. + The script editor's color for operators ([code]( ) [ ] { } + - * /[/code], ...). diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp index 7f3028f6155..f692aa4aec1 100644 --- a/editor/settings/editor_settings.cpp +++ b/editor/settings/editor_settings.cpp @@ -716,6 +716,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { "text_editor/theme/highlighting/comment_color", "text_editor/theme/highlighting/doc_comment_color", "text_editor/theme/highlighting/string_color", + "text_editor/theme/highlighting/string_placeholder_color", "text_editor/theme/highlighting/background_color", "text_editor/theme/highlighting/text_color", "text_editor/theme/highlighting/line_number_color", @@ -1711,6 +1712,7 @@ HashMap EditorSettings::get_godot2_text_editor_theme() { colors["text_editor/theme/highlighting/comment_color"] = Color(0.4, 0.4, 0.4); colors["text_editor/theme/highlighting/doc_comment_color"] = Color(0.5, 0.6, 0.7); colors["text_editor/theme/highlighting/string_color"] = Color(0.94, 0.43, 0.75); + colors["text_editor/theme/highlighting/string_placeholder_color"] = Color(1, 0.75, 0.4); colors["text_editor/theme/highlighting/background_color"] = Color(0.13, 0.12, 0.15); colors["text_editor/theme/highlighting/completion_background_color"] = Color(0.17, 0.16, 0.2); colors["text_editor/theme/highlighting/completion_selected_color"] = Color(0.26, 0.26, 0.27); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 4be86c4c9f9..9fe61dadcb2 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -506,6 +506,7 @@ void EditorThemeManager::_populate_text_editor_styles(const Ref &p_ colors["text_editor/theme/highlighting/comment_color"] = p_config.dark_icon_and_font ? dim_color : Color(0.08, 0.08, 0.08, 0.5); colors["text_editor/theme/highlighting/doc_comment_color"] = p_config.dark_icon_and_font ? Color(0.6, 0.7, 0.8, 0.8) : Color(0.15, 0.15, 0.4, 0.7); colors["text_editor/theme/highlighting/string_color"] = p_config.dark_icon_and_font ? Color(1, 0.93, 0.63) : Color(0.6, 0.42, 0); + colors["text_editor/theme/highlighting/string_placeholder_color"] = p_config.dark_icon_and_font ? Color(1, 0.75, 0.4) : Color(0.93, 0.6, 0.33); // Use the brightest background color on a light theme (which generally uses a negative contrast rate). colors["text_editor/theme/highlighting/background_color"] = p_config.dark_icon_and_font ? p_config.dark_color_2 : p_config.dark_color_3; diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index ec5a2303339..a6f03409d5f 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -244,18 +244,66 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l int region_end_index = -1; int end_key_length = color_regions[in_region].end_key.length(); const char32_t *end_key = color_regions[in_region].end_key.get_data(); + int placeholder_end = from; for (; from < line_length; from++) { if (line_length - from < end_key_length) { - // Don't break if '\' to highlight esc chars. - if (str.find_char('\\', from) < 0) { + // Don't break if '\' to highlight escape sequences, + // and '%' and '{' to highlight placeholders. + if (str.find_char('\\', from) == -1 && str.find_char('%', from) == -1 && str.find_char('{', from) == -1) { break; } } - if (!is_symbol(str[from])) { + if (!is_symbol(str[from]) && str[from] != '%') { continue; } + if (str[from] == '%' || str[from] == '{') { + int placeholder_start = from; + bool is_percent = str[from] == '%'; + + from++; + + if (is_percent) { + if (str[from] == '%') { + placeholder_end = from + 1; + } else { + const String allowed_chars = "+.-*0123456789"; + const String placeholder_types = "cdfosvxX"; + for (int i = 0; i < line_length - from; i++) { + if (allowed_chars.contains_char(str[from + i]) && + !placeholder_types.contains_char(str[from + i]) && + (str[from + i] != end_key[0] || (str[from + i] == end_key[0] && str[from + i - 1] == '\\'))) { + continue; + } + if (placeholder_types.contains_char(str[from + i])) { + placeholder_end = from + i + 1; + } + break; + } + } + } else { + for (int i = 0; i < line_length - from; i++) { + if (str[from + i] != '}' && (str[from + i] != end_key[0] || (str[from + i] == end_key[0] && str[from + i - 1] == '\\'))) { + continue; + } + if (str[from + i] == '}') { + placeholder_end = from + i + 1; + } + break; + } + } + + if (placeholder_end > placeholder_start) { + Dictionary placeholder_highlighter_info; + placeholder_highlighter_info["color"] = placeholder_color; + color_map[placeholder_start] = placeholder_highlighter_info; + Dictionary region_continue_highlighter_info; + region_continue_highlighter_info["color"] = region_color; + color_map[placeholder_end] = region_continue_highlighter_info; + } + } + if (str[from] == '\\') { if (!color_regions[in_region].r_prefix) { Dictionary escape_char_highlighter_info; @@ -280,7 +328,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } Dictionary region_continue_highlighter_info; - region_continue_highlighter_info["color"] = region_color; + region_continue_highlighter_info["color"] = from < placeholder_end ? placeholder_color : region_color; color_map[from + 1] = region_continue_highlighter_info; } @@ -315,6 +363,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l prev_is_binary_op = false; continue; } + color_map.sort(); // Prevents e.g. escape sequences from being overridden by string placeholders. } } @@ -813,6 +862,7 @@ void GDScriptSyntaxHighlighter::_update_cache() { /* Strings */ string_color = EDITOR_GET("text_editor/theme/highlighting/string_color"); + placeholder_color = EDITOR_GET("text_editor/theme/highlighting/string_placeholder_color"); add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color); add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color); add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color); diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h index 263c2a02377..e6116d74fb1 100644 --- a/modules/gdscript/editor/gdscript_highlighter.h +++ b/modules/gdscript/editor/gdscript_highlighter.h @@ -89,6 +89,7 @@ private: Color number_color; Color member_variable_color; Color string_color; + Color placeholder_color; Color node_path_color; Color node_ref_color; Color annotation_color;