Tree: Add per-cell autowrap_trim_flags to TreeItem

The Debugger Errors tab previously displayed ASCII art diagnostic output
(like Rust/Elm-style error messages) incorrectly because the Tree widget
hardcoded space trimming flags, causing leading spaces on wrapped lines
to be trimmed and breaking alignment.

This adds a per-cell `autowrap_trim_flags` property to TreeItem, following
the pattern used by Label and RichTextLabel. The debugger errors tab now
disables trim flags and uses the same monospace font as the Output panel,
ensuring proper alignment of structured error messages.
This commit is contained in:
Philippe Vaillancourt 2025-12-24 15:28:15 -05:00
parent 63227bbc8a
commit e046830c50
4 changed files with 51 additions and 1 deletions

View file

@ -94,6 +94,13 @@
Returns the text autowrap mode in the given [param column]. By default it is [constant TextServer.AUTOWRAP_OFF].
</description>
</method>
<method name="get_autowrap_trim_flags" qualifiers="const">
<return type="int" enum="TextServer.LineBreakFlag" is_bitfield="true" />
<param index="0" name="column" type="int" />
<description>
Returns the autowrap trim flags for the given [param column]. By default, both [constant TextServer.BREAK_TRIM_START_EDGE_SPACES] and [constant TextServer.BREAK_TRIM_END_EDGE_SPACES] are enabled.
</description>
</method>
<method name="get_button" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="column" type="int" />
@ -538,6 +545,14 @@
Sets the autowrap mode in the given [param column]. If set to something other than [constant TextServer.AUTOWRAP_OFF], the text gets wrapped inside the cell's bounding rectangle.
</description>
</method>
<method name="set_autowrap_trim_flags">
<return type="void" />
<param index="0" name="column" type="int" />
<param index="1" name="flags" type="int" enum="TextServer.LineBreakFlag" is_bitfield="true" />
<description>
Sets the autowrap trim flags for the given [param column]. These flags control whether leading and trailing spaces are trimmed on wrapped lines. Set to [code]0[/code] to disable all trimming.
</description>
</method>
<method name="set_button">
<return type="void" />
<param index="0" name="column" type="int" />

View file

@ -678,6 +678,7 @@ void ScriptEditorDebugger::_msg_error(uint64_t p_thread_id, const Array &p_data)
error_title += oe.error_descr.is_empty() ? oe.error : oe.error_descr;
error->set_text(1, error_title);
error->set_autowrap_mode(1, TextServer::AUTOWRAP_WORD_SMART);
error->set_autowrap_trim_flags(1, 0);
tooltip += " " + error_title + "\n";
// Find the language of the error's source file.
@ -1117,6 +1118,13 @@ void ScriptEditorDebugger::_notification(int p_what) {
reason->add_theme_color_override(SNAME("default_color"), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
reason->add_theme_style_override(SNAME("normal"), get_theme_stylebox(SNAME("normal"), SNAME("Label"))); // Empty stylebox.
const Ref<Font> source_font = get_theme_font(SNAME("output_source"), EditorStringName(EditorFonts));
if (source_font.is_valid()) {
error_tree->add_theme_font_override("font", source_font);
}
const int font_size = get_theme_font_size(SNAME("output_source_size"), EditorStringName(EditorFonts));
error_tree->add_theme_font_size_override("font_size", font_size);
TreeItem *error_root = error_tree->get_root();
if (error_root) {
TreeItem *error = error_root->get_first_child();

View file

@ -460,6 +460,26 @@ TextServer::AutowrapMode TreeItem::get_autowrap_mode(int p_column) const {
return cells[p_column].autowrap_mode;
}
void TreeItem::set_autowrap_trim_flags(int p_column, BitField<TextServer::LineBreakFlag> p_flags) {
ERR_FAIL_INDEX(p_column, cells.size());
// Only trim-related flags are valid for this property.
BitField<TextServer::LineBreakFlag> masked_flags = p_flags & TextServer::BREAK_TRIM_MASK;
if (cells[p_column].autowrap_trim_flags == masked_flags) {
return;
}
cells.write[p_column].autowrap_trim_flags = masked_flags;
cells.write[p_column].dirty = true;
_changed_notify(p_column);
cells.write[p_column].cached_minimum_size_dirty = true;
}
BitField<TextServer::LineBreakFlag> TreeItem::get_autowrap_trim_flags(int p_column) const {
ERR_FAIL_INDEX_V(p_column, cells.size(), TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES);
return cells[p_column].autowrap_trim_flags;
}
void TreeItem::set_text_overrun_behavior(int p_column, TextServer::OverrunBehavior p_behavior) {
ERR_FAIL_INDEX(p_column, cells.size());
@ -1810,6 +1830,9 @@ void TreeItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "column", "autowrap_mode"), &TreeItem::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode", "column"), &TreeItem::get_autowrap_mode);
ClassDB::bind_method(D_METHOD("set_autowrap_trim_flags", "column", "flags"), &TreeItem::set_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("get_autowrap_trim_flags", "column"), &TreeItem::get_autowrap_trim_flags);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "column", "overrun_behavior"), &TreeItem::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior", "column"), &TreeItem::get_text_overrun_behavior);
@ -2218,7 +2241,7 @@ void Tree::update_item_cell(TreeItem *p_item, int p_col) const {
const String &lang = p_item->cells[p_col].language.is_empty() ? _get_locale() : p_item->cells[p_col].language;
p_item->cells.write[p_col].text_buf->add_string(valtext, font, font_size, lang);
BitField<TextServer::LineBreakFlag> break_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES;
BitField<TextServer::LineBreakFlag> break_flags = TextServer::BREAK_MANDATORY | p_item->cells[p_col].autowrap_trim_flags;
switch (p_item->cells.write[p_col].autowrap_mode) {
case TextServer::AUTOWRAP_OFF:
break;

View file

@ -78,6 +78,7 @@ private:
Array st_args;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_INHERITED;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::LineBreakFlag> autowrap_trim_flags = TextServer::BREAK_TRIM_START_EDGE_SPACES | TextServer::BREAK_TRIM_END_EDGE_SPACES;
bool dirty = true;
double min = 0.0;
double max = 100.0;
@ -284,6 +285,9 @@ public:
void set_autowrap_mode(int p_column, TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode(int p_column) const;
void set_autowrap_trim_flags(int p_column, BitField<TextServer::LineBreakFlag> p_flags);
BitField<TextServer::LineBreakFlag> get_autowrap_trim_flags(int p_column) const;
void set_text_overrun_behavior(int p_column, TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior(int p_column) const;