diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 59a74b5cd1a..fbabdf9c816 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -746,6 +746,9 @@ If [code]true[/code], displays line numbers in a gutter at the left. + + If [member text_editor/appearance/lines/word_wrap] is set to [code]1[/code], sets text wrapping mode. To see how each mode behaves, see [enum TextServer.AutowrapMode]. + If [code]true[/code], displays the folding arrows next to indented code sections and allows code folding. If [code]false[/code], hides the folding arrows next to indented code sections and disallows code folding. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index d042a0b9a74..55a5ea18f20 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -1088,6 +1088,9 @@ + + If [member wrap_mode] is set to [constant LINE_WRAPPING_BOUNDARY], sets text wrapping mode. To see how each mode behaves, see [enum TextServer.AutowrapMode]. + If [code]true[/code], makes the caret blink. diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 821d1c54e0c..b48bd4ab5fa 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1035,6 +1035,7 @@ void CodeTextEditor::update_editor_settings() { text_editor->set_line_folding_enabled(EDITOR_GET("text_editor/appearance/lines/code_folding")); text_editor->set_draw_fold_gutter(EDITOR_GET("text_editor/appearance/lines/code_folding")); text_editor->set_line_wrapping_mode((TextEdit::LineWrappingMode)EDITOR_GET("text_editor/appearance/lines/word_wrap").operator int()); + text_editor->set_autowrap_mode((TextServer::AutowrapMode)EDITOR_GET("text_editor/appearance/lines/autowrap_mode").operator int()); // Appearance: Whitespace text_editor->set_draw_tabs(EDITOR_GET("text_editor/appearance/whitespace/draw_tabs")); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 5deb5908752..73168831326 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -562,6 +562,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // Appearance: Lines _initial_set("text_editor/appearance/lines/code_folding", true); EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/lines/word_wrap", 0, "None,Boundary") + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/lines/autowrap_mode", 3, "Arbitrary:1,Word:2,Word (Smart):3") // Appearance: Whitespace _initial_set("text_editor/appearance/whitespace/draw_tabs", true); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 039fa46bb0a..3b2013f7ecf 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -112,6 +112,14 @@ float TextEdit::Text::get_width() const { return width; } +void TextEdit::Text::set_brk_flags(BitField p_flags) { + brk_flags = p_flags; +} + +BitField TextEdit::Text::get_brk_flags() const { + return brk_flags; +} + int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); @@ -180,6 +188,7 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan text.write[p_line].data_buf->set_width(width); text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); + text.write[p_line].data_buf->set_break_flags(brk_flags); text.write[p_line].data_buf->set_preserve_control(draw_control_chars); if (p_ime_text.length() > 0) { if (p_text_changed) { @@ -247,21 +256,19 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { text.write[i].data_buf->set_width(width); + text.write[i].data_buf->set_break_flags(brk_flags); if (tab_size_dirty) { if (tab_size > 0) { Vector tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); text.write[i].data_buf->tab_align(tabs); } - // Tabs have changes, force width update. - text.write[i].width = get_line_width(i); } + text.write[i].width = get_line_width(i); } + tab_size_dirty = false; - if (tab_size_dirty) { - _calculate_max_line_width(); - tab_size_dirty = false; - } + _calculate_max_line_width(); } void TextEdit::Text::invalidate_font() { @@ -2935,6 +2942,7 @@ void TextEdit::_update_placeholder() { // Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now. placeholder_data_buf->clear(); placeholder_data_buf->set_width(text.get_width()); + placeholder_data_buf->set_break_flags(text.get_brk_flags()); placeholder_data_buf->set_direction((TextServer::Direction)text_direction); placeholder_data_buf->set_preserve_control(draw_control_chars); placeholder_data_buf->add_string(placeholder_text, theme_cache.font, theme_cache.font_size, language); @@ -5256,6 +5264,7 @@ void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) { if (line_wrapping_mode != p_wrapping_mode) { line_wrapping_mode = p_wrapping_mode; _update_wrap_at_column(true); + queue_redraw(); } } @@ -5263,6 +5272,22 @@ TextEdit::LineWrappingMode TextEdit::get_line_wrapping_mode() const { return line_wrapping_mode; } +void TextEdit::set_autowrap_mode(TextServer::AutowrapMode p_mode) { + if (autowrap_mode == p_mode) { + return; + } + + autowrap_mode = p_mode; + if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) { + _update_wrap_at_column(true); + queue_redraw(); + } +} + +TextServer::AutowrapMode TextEdit::get_autowrap_mode() const { + return autowrap_mode; +} + bool TextEdit::is_line_wrapped(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { @@ -5540,37 +5565,32 @@ void TextEdit::adjust_viewport_to_caret(int p_caret) { } visible_width -= 20; // Give it a little more space. - if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { - // Adjust x offset. - Vector2i caret_pos; + Vector2i caret_pos; - // Get position of the start of caret. - if (ime_text.length() != 0 && ime_selection.x != 0) { - caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); + // Get position of the start of caret. + if (ime_text.length() != 0 && ime_selection.x != 0) { + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret)); + } else { + caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); + } + + // Get position of the end of caret. + if (ime_text.length() != 0) { + if (ime_selection.y != 0) { + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); } else { - caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret)); - } - - // Get position of the end of caret. - if (ime_text.length() != 0) { - if (ime_selection.y != 0) { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret)); - } else { - caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); - } - } else { - caret_pos.y = caret_pos.x; - } - - if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { - first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; - } - - if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { - first_visible_col = MIN(caret_pos.x, caret_pos.y); + caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret)); } } else { - first_visible_col = 0; + caret_pos.y = caret_pos.x; + } + + if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) { + first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1; + } + + if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) { + first_visible_col = MIN(caret_pos.x, caret_pos.y); } h_scroll->set_value(first_visible_col); @@ -6280,6 +6300,9 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_line_wrapping_mode", "mode"), &TextEdit::set_line_wrapping_mode); ClassDB::bind_method(D_METHOD("get_line_wrapping_mode"), &TextEdit::get_line_wrapping_mode); + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &TextEdit::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &TextEdit::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("is_line_wrapped", "line"), &TextEdit::is_line_wrapped); ClassDB::bind_method(D_METHOD("get_line_wrap_count", "line"), &TextEdit::get_line_wrap_count); ClassDB::bind_method(D_METHOD("get_line_wrap_index_at_column", "line", "column"), &TextEdit::get_line_wrap_index_at_column); @@ -6415,6 +6438,7 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); @@ -7222,10 +7246,26 @@ void TextEdit::_update_wrap_at_column(bool p_force) { if ((wrap_at_column != new_wrap_at) || p_force) { wrap_at_column = new_wrap_at; if (line_wrapping_mode) { + BitField autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_OFF: + break; + } + text.set_brk_flags(autowrap_flags); text.set_width(wrap_at_column); } else { text.set_width(-1); } + text.invalidate_all_lines(); _update_placeholder(); } @@ -7293,7 +7333,7 @@ void TextEdit::_update_scrollbars() { v_scroll->hide(); } - if (total_width > visible_width && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) { + if (total_width > visible_width) { h_scroll->show(); h_scroll->set_max(total_width); h_scroll->set_page(visible_width); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 6eed200a288..83f6d58bea1 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -172,6 +172,7 @@ private: String language; TextServer::Direction direction = TextServer::DIRECTION_AUTO; + BitField brk_flags = TextServer::BREAK_MANDATORY; bool draw_control_chars = false; int line_height = -1; @@ -198,6 +199,8 @@ private: void set_width(float p_width); float get_width() const; + void set_brk_flags(BitField p_flags); + BitField get_brk_flags() const; int get_line_wrap_amount(int p_line) const; Vector get_line_wrap_ranges(int p_line) const; @@ -460,6 +463,7 @@ private: /* Line wrapping. */ LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; + TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_WORD_SMART; int wrap_at_column = 0; int wrap_right_offset = 10; @@ -894,6 +898,9 @@ public: void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); LineWrappingMode get_line_wrapping_mode() const; + void set_autowrap_mode(TextServer::AutowrapMode p_mode); + TextServer::AutowrapMode get_autowrap_mode() const; + bool is_line_wrapped(int p_line) const; int get_line_wrap_count(int p_line) const; int get_line_wrap_index_at_column(int p_line, int p_column) const; diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 67d473128ee..345e6172856 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -3827,10 +3827,10 @@ TEST_CASE("[SceneTree][TextEdit] viewport") { text_edit->adjust_viewport_to_caret(); MessageQueue::get_singleton()->flush(); - CHECK(text_edit->get_first_visible_line() == (visible_lines / 2) + 4); - CHECK(text_edit->get_v_scroll() == (visible_lines + (visible_lines / 2)) - 1); - CHECK(text_edit->get_last_full_visible_line() == (visible_lines) + 3); - CHECK(text_edit->get_last_full_visible_line_wrap_index() == 1); + CHECK(text_edit->get_first_visible_line() == (visible_lines / 2) + 6); + CHECK(text_edit->get_v_scroll() == (visible_lines + (visible_lines / 2)) + 1); + CHECK(text_edit->get_last_full_visible_line() == (visible_lines) + 5); + CHECK(text_edit->get_last_full_visible_line_wrap_index() == 0); CHECK(text_edit->get_caret_wrap_index() == 1); text_edit->center_viewport_to_caret();