Merge pull request #49238 from Paulb23/code_edit_code_folding

Move code folding into CodeEdit and hide line hiding API
This commit is contained in:
Rémi Verschelde 2021-06-16 20:11:39 +02:00 committed by GitHub
commit 341cb8da31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 385 additions and 306 deletions

View file

@ -86,6 +86,15 @@
Line only denotes if the region should continue until the end of the line or carry over on to the next line. If the end key is blank this is automatically set to [code]true[/code].
</description>
</method>
<method name="can_fold_line" qualifiers="const">
<return type="bool">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Returns if the given line is foldable, that is, it has indented lines right below it or a comment / string block.
</description>
</method>
<method name="cancel_code_completion">
<return type="void">
</return>
@ -134,6 +143,22 @@
Inserts the selected entry into the text. If [code]replace[/code] is true, any existing text is replaced rather then merged.
</description>
</method>
<method name="fold_all_lines">
<return type="void">
</return>
<description>
Folds all lines that are possible to be folded (see [method can_fold_line]).
</description>
</method>
<method name="fold_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Folds the given line, if possible (see [method can_fold_line]).
</description>
</method>
<method name="get_bookmarked_lines" qualifiers="const">
<return type="Array">
</return>
@ -221,6 +246,13 @@
<description>
</description>
</method>
<method name="get_folded_lines" qualifiers="const">
<return type="int[]">
</return>
<description>
Return all lines that are current folded.
</description>
</method>
<method name="get_text_for_code_completion" qualifiers="const">
<return type="String">
</return>
@ -292,6 +324,15 @@
<description>
</description>
</method>
<method name="is_line_folded" qualifiers="const">
<return type="bool">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Returns whether the line at the specified index is folded or not.
</description>
</method>
<method name="remove_comment_delimiter">
<return type="void">
</return>
@ -376,6 +417,30 @@
<description>
</description>
</method>
<method name="toggle_foldable_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Toggle the folding of the code block at the given line.
</description>
</method>
<method name="unfold_all_lines">
<return type="void">
</return>
<description>
</description>
</method>
<method name="unfold_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Unfolds all lines that were previously folded.
</description>
</method>
<method name="update_code_completion_options">
<return type="void">
</return>
@ -411,6 +476,9 @@
<member name="draw_line_numbers" type="bool" setter="set_draw_line_numbers" getter="is_draw_line_numbers_enabled" default="false">
</member>
<member name="layout_direction" type="int" setter="set_layout_direction" getter="get_layout_direction" override="true" enum="Control.LayoutDirection" default="2" />
<member name="line_folding" type="bool" setter="set_line_folding_enabled" getter="is_line_folding_enabled" default="true">
Sets whether line folding is allowed.
</member>
<member name="structured_text_bidi_override_options" type="Array" setter="set_structured_text_bidi_override_options" getter="get_structured_text_bidi_override_options" override="true" default="[ ]" />
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" override="true" enum="Control.TextDirection" default="1" />
<member name="zero_pad_line_numbers" type="bool" setter="set_line_numbers_zero_padded" getter="is_line_numbers_zero_padded" default="false">
@ -500,6 +568,8 @@
</theme_item>
<theme_item name="folded" type="Texture2D">
</theme_item>
<theme_item name="folded_eol_icon" type="Texture2D">
</theme_item>
<theme_item name="font" type="Font">
</theme_item>
<theme_item name="font_color" type="Color" default="Color( 0.88, 0.88, 0.88, 1 )">

View file

@ -18,15 +18,6 @@
<description>
</description>
</method>
<method name="can_fold" qualifiers="const">
<return type="bool">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Returns if the given line is foldable, that is, it has indented lines right below it.
</description>
</method>
<method name="center_viewport_to_cursor">
<return type="void">
</return>
@ -95,7 +86,7 @@
<description>
Moves the cursor at the specified [code]line[/code] index.
If [code]adjust_viewport[/code] is set to [code]true[/code], the viewport will center at the cursor position after the move occurs.
If [code]can_be_hidden[/code] is set to [code]true[/code], the specified [code]line[/code] can be hidden using [method set_line_as_hidden].
If [code]can_be_hidden[/code] is set to [code]true[/code], the specified [code]line[/code] can be hidden.
</description>
</method>
<method name="cut">
@ -112,22 +103,6 @@
Deselects the current selection.
</description>
</method>
<method name="fold_all_lines">
<return type="void">
</return>
<description>
Folds all lines that are possible to be folded (see [method can_fold]).
</description>
</method>
<method name="fold_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Folds the given line, if possible (see [method can_fold]).
</description>
</method>
<method name="get_caret_draw_pos" qualifiers="const">
<return type="Vector2">
</return>
@ -329,15 +304,6 @@
Returns [code]true[/code] if the caret is visible on the screen.
</description>
</method>
<method name="is_folded" qualifiers="const">
<return type="bool">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Returns whether the line at the specified index is folded or not.
</description>
</method>
<method name="is_gutter_clickable" qualifiers="const">
<return type="bool">
</return>
@ -372,15 +338,6 @@
<description>
</description>
</method>
<method name="is_line_hidden" qualifiers="const">
<return type="bool">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Returns whether the line at the specified index is hidden or not.
</description>
</method>
<method name="is_selection_active" qualifiers="const">
<return type="bool">
</return>
@ -560,17 +517,6 @@
Sets the text for a specific line.
</description>
</method>
<method name="set_line_as_hidden">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<argument index="1" name="enable" type="bool">
</argument>
<description>
If [code]true[/code], hides the line of the specified index.
</description>
</method>
<method name="set_line_background_color">
<return type="void">
</return>
@ -665,15 +611,6 @@
<description>
</description>
</method>
<method name="toggle_fold_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Toggle the folding of the code block at the given line.
</description>
</method>
<method name="undo">
<return type="void">
</return>
@ -681,22 +618,6 @@
Perform undo operation.
</description>
</method>
<method name="unfold_line">
<return type="void">
</return>
<argument index="0" name="line" type="int">
</argument>
<description>
Unfolds the given line, if folded.
</description>
</method>
<method name="unhide_all_lines">
<return type="void">
</return>
<description>
Unhide all lines that were previously set to hidden by [method set_line_as_hidden].
</description>
</method>
</methods>
<members>
<member name="caret_blink" type="bool" setter="cursor_set_blink_enabled" getter="cursor_get_blink_enabled" default="false">
@ -730,9 +651,6 @@
If [code]true[/code], the "tab" character will have a visible representation.
</member>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" override="true" enum="Control.FocusMode" default="2" />
<member name="hiding_enabled" type="bool" setter="set_hiding_enabled" getter="is_hiding_enabled" default="false">
If [code]true[/code], all lines that have been set to hidden by [method set_line_as_hidden], will not be visible.
</member>
<member name="highlight_all_occurrences" type="bool" setter="set_highlight_all_occurrences" getter="is_highlight_all_occurrences_enabled" default="false">
If [code]true[/code], all occurrences of the selected text will be highlighted.
</member>
@ -972,8 +890,6 @@
</theme_item>
<theme_item name="caret_color" type="Color" default="Color( 0.88, 0.88, 0.88, 1 )">
</theme_item>
<theme_item name="code_folding_color" type="Color" default="Color( 0.8, 0.8, 0.8, 0.8 )">
</theme_item>
<theme_item name="current_line_color" type="Color" default="Color( 0.25, 0.25, 0.26, 0.8 )">
Sets the [Color] of the breakpoints. [member breakpoint_gutter] has to be enabled.
</theme_item>

View file

@ -940,7 +940,7 @@ void CodeTextEditor::update_editor_settings() {
text_editor->set_draw_line_numbers(EditorSettings::get_singleton()->get("text_editor/appearance/show_line_numbers"));
text_editor->set_line_numbers_zero_padded(EditorSettings::get_singleton()->get("text_editor/appearance/line_numbers_zero_padded"));
text_editor->set_draw_bookmarks_gutter(EditorSettings::get_singleton()->get("text_editor/appearance/show_bookmark_gutter"));
text_editor->set_hiding_enabled(EditorSettings::get_singleton()->get("text_editor/appearance/code_folding"));
text_editor->set_line_folding_enabled(EditorSettings::get_singleton()->get("text_editor/appearance/code_folding"));
text_editor->set_draw_fold_gutter(EditorSettings::get_singleton()->get("text_editor/appearance/code_folding"));
text_editor->set_wrap_enabled(EditorSettings::get_singleton()->get("text_editor/appearance/word_wrap"));
text_editor->set_show_line_length_guidelines(EditorSettings::get_singleton()->get("text_editor/appearance/show_line_length_guidelines"));
@ -1502,6 +1502,7 @@ void CodeTextEditor::set_error_pos(int p_line, int p_column) {
void CodeTextEditor::goto_error() {
if (error->get_text() != "") {
text_editor->unfold_line(error_line);
text_editor->cursor_set_line(error_line);
text_editor->cursor_set_column(error_column);
text_editor->center_viewport_to_cursor();

View file

@ -1061,7 +1061,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
code_editor->clone_lines_down();
} break;
case EDIT_TOGGLE_FOLD_LINE: {
tx->toggle_fold_line(tx->cursor_get_line());
tx->toggle_foldable_line(tx->cursor_get_line());
tx->update();
} break;
case EDIT_FOLD_ALL_LINES: {
@ -1549,7 +1549,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
}
bool has_color = (word_at_pos == "Color");
bool foldable = tx->can_fold(row) || tx->is_folded(row);
bool foldable = tx->can_fold_line(row) || tx->is_line_folded(row);
bool open_docs = false;
bool goto_definition = false;

View file

@ -332,7 +332,7 @@ void TextEditor::_edit_option(int p_op) {
code_editor->clone_lines_down();
} break;
case EDIT_TOGGLE_FOLD_LINE: {
tx->toggle_fold_line(tx->cursor_get_line());
tx->toggle_foldable_line(tx->cursor_get_line());
tx->update();
} break;
case EDIT_FOLD_ALL_LINES: {
@ -432,8 +432,8 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col);
tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret"));
bool can_fold = tx->can_fold(row);
bool is_folded = tx->is_folded(row);
bool can_fold = tx->can_fold_line(row);
bool is_folded = tx->is_line_folded(row);
if (tx->is_right_click_moving_caret()) {
if (tx->is_selection_active()) {
@ -463,7 +463,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_MENU) {
CodeEdit *tx = code_editor->get_text_editor();
int line = tx->cursor_get_line();
_make_context_menu(tx->is_selection_active(), tx->can_fold(line), tx->is_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos()));
_make_context_menu(tx->is_selection_active(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos()));
context_menu->grab_focus();
}
}

View file

@ -218,6 +218,11 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
/* Ignore mouse clicks in IME input mode. */
if (has_ime_text()) {
return;
}
if (code_completion_active && code_completion_rect.has_point(mb->get_position())) {
if (!mb->is_pressed()) {
return;
@ -248,6 +253,30 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
}
cancel_code_completion();
set_code_hint("");
if (mb->is_pressed()) {
Vector2i mpos = mb->get_position();
if (is_layout_rtl()) {
mpos.x = get_size().x - mpos.x;
}
int line, col;
_get_mouse_pos(Point2i(mpos.x, mpos.y), line, col);
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (is_line_folded(line)) {
int wrap_index = get_line_wrap_index_at_col(line, col);
if (wrap_index == times_line_wraps(line)) {
int eol_icon_width = cache.folded_eol_icon->get_width();
int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
if (mpos.x > left_margin && mpos.x <= left_margin + eol_icon_width + 3) {
unfold_line(line);
return;
}
}
}
}
}
}
Ref<InputEventKey> k = p_gui_input;
@ -356,6 +385,16 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
set_code_hint("");
}
/* Override input to unfold lines where needed. */
if (!is_readonly()) {
if (k->is_action("ui_text_newline_above", true) || k->is_action("ui_text_newline_blank", true) || k->is_action("ui_text_newline", true)) {
unfold_line(cursor_get_line());
}
if (cursor_get_line() > 0 && k->is_action("ui_text_backspace", true)) {
unfold_line(cursor_get_line() - 1);
}
}
/* Remove shift otherwise actions will not match. */
k = k->duplicate();
k->set_shift_pressed(false);
@ -380,6 +419,21 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (is_readonly() && (!is_selecting_enabled() || get_line_count() == 0))) {
return CURSOR_ARROW;
}
int line, col;
_get_mouse_pos(p_pos, line, col);
if (is_line_folded(line)) {
int wrap_index = get_line_wrap_index_at_col(line, col);
if (wrap_index == times_line_wraps(line)) {
int eol_icon_width = cache.folded_eol_icon->get_width();
int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
if (p_pos.x > left_margin && p_pos.x <= left_margin + eol_icon_width + 3) {
return CURSOR_POINTING_HAND;
}
}
}
return TextEdit::get_cursor_shape(p_pos);
}
@ -581,7 +635,7 @@ bool CodeEdit::is_drawing_fold_gutter() const {
}
void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region) {
if (!can_fold(p_line) && !is_folded(p_line)) {
if (!can_fold_line(p_line) && !is_line_folded(p_line)) {
set_line_gutter_clickable(p_line, fold_gutter, false);
return;
}
@ -593,13 +647,193 @@ void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_regi
p_region.position += Point2(horizontal_padding, vertical_padding);
p_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
if (can_fold(p_line)) {
if (can_fold_line(p_line)) {
can_fold_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
return;
}
folded_icon->draw_rect(get_canvas_item(), p_region, false, folding_color);
}
/* Line Folding */
void CodeEdit::set_line_folding_enabled(bool p_enabled) {
line_folding_enabled = p_enabled;
set_hiding_enabled(p_enabled);
}
bool CodeEdit::is_line_folding_enabled() const {
return line_folding_enabled;
}
bool CodeEdit::can_fold_line(int p_line) const {
ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
if (!line_folding_enabled) {
return false;
}
if (p_line + 1 >= get_line_count() || get_line(p_line).strip_edges().size() == 0) {
return false;
}
if (is_line_hidden(p_line) || is_line_folded(p_line)) {
return false;
}
/* Check for full multiline line or block strings / comments. */
int in_comment = is_in_comment(p_line);
int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
if (in_string != -1 || in_comment != -1) {
if (get_delimiter_start_position(p_line, get_line(p_line).size() - 1).y != p_line) {
return false;
}
int delimter_end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
/* No end line, therefore we have a multiline region over the rest of the file. */
if (delimter_end_line == -1) {
return true;
}
/* End line is the same therefore we have a block. */
if (delimter_end_line == p_line) {
/* Check we are the start of the block. */
if (p_line - 1 >= 0) {
if ((in_string != -1 && is_in_string(p_line - 1) != -1) || (in_comment != -1 && is_in_comment(p_line - 1) != -1)) {
return false;
}
}
/* Check it continues for at least one line. */
return ((in_string != -1 && is_in_string(p_line + 1) != -1) || (in_comment != -1 && is_in_comment(p_line + 1) != -1));
}
return ((in_string != -1 && is_in_string(delimter_end_line) != -1) || (in_comment != -1 && is_in_comment(delimter_end_line) != -1));
}
/* Otherwise check indent levels. */
int start_indent = get_indent_level(p_line);
for (int i = p_line + 1; i < get_line_count(); i++) {
if (is_in_string(i) != -1 || is_in_comment(i) != -1 || get_line(i).strip_edges().size() == 0) {
continue;
}
return (get_indent_level(i) > start_indent);
}
return false;
}
void CodeEdit::fold_line(int p_line) {
ERR_FAIL_INDEX(p_line, get_line_count());
if (!is_line_folding_enabled() || !can_fold_line(p_line)) {
return;
}
/* Find the last line to be hidden. */
int end_line = get_line_count();
int in_comment = is_in_comment(p_line);
int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
if (in_string != -1 || in_comment != -1) {
end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
/* End line is the same therefore we have a block. */
if (end_line == p_line) {
for (int i = p_line + 1; i < get_line_count(); i++) {
if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) {
end_line = i - 1;
break;
}
}
}
} else {
int start_indent = get_indent_level(p_line);
for (int i = p_line + 1; i < get_line_count(); i++) {
if (get_line(p_line).strip_edges().size() == 0 || is_in_string(i) != -1 || is_in_comment(i) != -1) {
end_line = i;
continue;
}
if (get_indent_level(i) <= start_indent && get_line(i).strip_edges().size() != 0) {
end_line = i - 1;
break;
}
}
}
for (int i = p_line + 1; i <= end_line; i++) {
set_line_as_hidden(i, true);
}
/* Fix selection. */
if (is_selection_active()) {
if (is_line_hidden(get_selection_from_line()) && is_line_hidden(get_selection_to_line())) {
deselect();
} else if (is_line_hidden(get_selection_from_line())) {
select(p_line, 9999, get_selection_to_line(), get_selection_to_column());
} else if (is_line_hidden(get_selection_to_line())) {
select(get_selection_from_line(), get_selection_from_column(), p_line, 9999);
}
}
/* Reset caret. */
if (is_line_hidden(cursor_get_line())) {
cursor_set_line(p_line, false, false);
cursor_set_column(get_line(p_line).length(), false);
}
update();
}
void CodeEdit::unfold_line(int p_line) {
ERR_FAIL_INDEX(p_line, get_line_count());
if (!is_line_folded(p_line) && !is_line_hidden(p_line)) {
return;
}
int fold_start = p_line;
for (; fold_start > 0; fold_start--) {
if (is_line_folded(fold_start)) {
break;
}
}
fold_start = is_line_folded(fold_start) ? fold_start : p_line;
for (int i = fold_start + 1; i < get_line_count(); i++) {
if (!is_line_hidden(i)) {
break;
}
set_line_as_hidden(i, false);
}
update();
}
void CodeEdit::fold_all_lines() {
for (int i = 0; i < get_line_count(); i++) {
fold_line(i);
}
update();
}
void CodeEdit::unfold_all_lines() {
unhide_all_lines();
}
void CodeEdit::toggle_foldable_line(int p_line) {
ERR_FAIL_INDEX(p_line, get_line_count());
if (is_line_folded(p_line)) {
unfold_line(p_line);
return;
}
fold_line(p_line);
}
bool CodeEdit::is_line_folded(int p_line) const {
ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
return p_line + 1 < get_line_count() && !is_line_hidden(p_line) && is_line_hidden(p_line + 1);
}
TypedArray<int> CodeEdit::get_folded_lines() const {
TypedArray<int> folded_lines;
for (int i = 0; i < get_line_count(); i++) {
if (is_line_folded(i)) {
folded_lines.push_back(i);
}
}
return folded_lines;
}
/* Delimiters */
// Strings
void CodeEdit::add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) {
@ -1094,6 +1328,21 @@ void CodeEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter);
ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter);
/* Line folding */
ClassDB::bind_method(D_METHOD("set_line_folding_enabled", "enabled"), &CodeEdit::set_line_folding_enabled);
ClassDB::bind_method(D_METHOD("is_line_folding_enabled"), &CodeEdit::is_line_folding_enabled);
ClassDB::bind_method(D_METHOD("can_fold_line", "line"), &CodeEdit::can_fold_line);
ClassDB::bind_method(D_METHOD("fold_line", "line"), &CodeEdit::fold_line);
ClassDB::bind_method(D_METHOD("unfold_line", "line"), &CodeEdit::unfold_line);
ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines);
ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines);
ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line);
ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded);
ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines);
/* Delimiters */
// Strings
ClassDB::bind_method(D_METHOD("add_string_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_string_delimiter, DEFVAL(false));
@ -1175,6 +1424,8 @@ void CodeEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled");
ADD_GROUP("Delimiters", "delimiter_");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_comments"), "set_comment_delimiters", "get_comment_delimiters");
@ -1205,9 +1456,9 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
}
if (p_gutter == fold_gutter) {
if (is_folded(p_line)) {
if (is_line_folded(p_line)) {
unfold_line(p_line);
} else if (can_fold(p_line)) {
} else if (can_fold_line(p_line)) {
fold_line(p_line);
}
return;

View file

@ -98,6 +98,9 @@ private:
void _gutter_clicked(int p_line, int p_gutter);
void _update_gutter_indexes();
/* Line Folding */
bool line_folding_enabled = true;
/* Delimiters */
enum DelimiterType {
TYPE_STRING,
@ -241,6 +244,21 @@ public:
void set_draw_fold_gutter(bool p_draw);
bool is_drawing_fold_gutter() const;
/* Line Folding */
void set_line_folding_enabled(bool p_enabled);
bool is_line_folding_enabled() const;
bool can_fold_line(int p_line) const;
void fold_line(int p_line);
void unfold_line(int p_line);
void fold_all_lines();
void unfold_all_lines();
void toggle_foldable_line(int p_line);
bool is_line_folded(int p_line) const;
TypedArray<int> get_folded_lines() const;
/* Delimiters */
void add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false);
void remove_string_delimiter(const String &p_start_key);

View file

@ -137,8 +137,11 @@ void TextEdit::Text::set_draw_control_chars(bool p_draw_control_chars) {
draw_control_chars = p_draw_control_chars;
}
int TextEdit::Text::get_line_width(int p_line) const {
int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
if (p_wrap_index != -1) {
return text[p_line].data_buf->get_line_width(p_wrap_index);
}
return text[p_line].data_buf->get_size().x;
}
@ -1354,7 +1357,8 @@ void TextEdit::_notification(int p_what) {
}
}
if (line_wrap_index == line_wrap_amount && is_folded(line)) {
// is_line_folded
if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && is_line_hidden(line + 1)) {
int xofs = char_ofs + char_margin + ofs_x + (cache.folded_eol_icon->get_width() / 2);
if (xofs >= xmargin_beg && xofs < xmargin_end) {
int yofs = (text_height - cache.folded_eol_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
@ -1943,10 +1947,6 @@ void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
}
}
if (is_folded(cursor.line)) {
unfold_line(cursor.line);
}
bool brace_indent = false;
// No need to indent if we are going upwards.
@ -2361,10 +2361,6 @@ void TextEdit::_backspace(bool p_word, bool p_all_to_left) {
cursor_set_line(line, false);
cursor_set_column(column);
} else {
// One character.
if (cursor.line > 0 && is_line_hidden(cursor.line - 1)) {
unfold_line(cursor.line - 1);
}
backspace_at_cursor();
}
}
@ -2692,15 +2688,6 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
left_margin += gutters[i].width;
}
// Unfold on folded icon click.
if (is_folded(row)) {
left_margin += gutter_padding + text.get_line_width(row) - cursor.x_ofs;
if (mpos.x > left_margin && mpos.x <= left_margin + cache.folded_eol_icon->get_width() + 3) {
unfold_line(row);
return;
}
}
// minimap
if (draw_minimap) {
_update_minimap_click();
@ -3703,10 +3690,6 @@ void TextEdit::center_viewport_to_cursor() {
scrolling = false;
minimap_clicked = false;
if (is_line_hidden(cursor.line)) {
unfold_line(cursor.line);
}
set_line_as_center_visible(cursor.line, get_cursor_wrap_index());
int visible_width = get_size().width - cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding - cache.minimap_width;
if (v_scroll->is_visible_in_tree()) {
@ -4124,15 +4107,6 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) {
return CURSOR_ARROW;
}
// EOL fold icon.
if (is_folded(row)) {
gutter += gutter_padding + text.get_line_width(row) - cursor.x_ofs;
if (p_pos.x > gutter - 3 && p_pos.x <= gutter + cache.folded_eol_icon->get_width() + 3) {
return CURSOR_POINTING_HAND;
}
}
return get_default_cursor_shape();
}
@ -4318,6 +4292,10 @@ String TextEdit::get_line(int line) const {
return text[line];
};
bool TextEdit::has_ime_text() const {
return !ime_text.is_empty();
}
void TextEdit::_clear() {
clear_undo_history();
text.clear();
@ -4404,7 +4382,7 @@ void TextEdit::_update_caches() {
cache.selection_color = get_theme_color("selection_color");
cache.current_line_color = get_theme_color("current_line_color");
cache.line_length_guideline_color = get_theme_color("line_length_guideline_color");
cache.code_folding_color = get_theme_color("code_folding_color");
cache.code_folding_color = get_theme_color("code_folding_color", "CodeEdit");
cache.brace_mismatch_color = get_theme_color("brace_mismatch_color");
cache.word_highlighted_color = get_theme_color("word_highlighted_color");
cache.search_result_color = get_theme_color("search_result_color");
@ -4417,7 +4395,7 @@ void TextEdit::_update_caches() {
#endif
cache.tab_icon = get_theme_icon("tab");
cache.space_icon = get_theme_icon("space");
cache.folded_eol_icon = get_theme_icon("GuiEllipsis", "EditorIcons");
cache.folded_eol_icon = get_theme_icon("folded_eol_icon", "CodeEdit");
TextServer::Direction dir;
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
@ -4526,6 +4504,10 @@ int TextEdit::get_gutter_width(int p_gutter) const {
return gutters[p_gutter].width;
}
int TextEdit::get_total_gutter_width() const {
return gutters_width + gutter_padding;
}
void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) {
ERR_FAIL_INDEX(p_gutter, gutters.size());
gutters.write[p_gutter].draw = p_draw;
@ -5107,14 +5089,6 @@ bool TextEdit::is_line_hidden(int p_line) const {
return text.is_hidden(p_line);
}
void TextEdit::fold_all_lines() {
for (int i = 0; i < text.size(); i++) {
fold_line(i);
}
_update_scrollbars();
update();
}
void TextEdit::unhide_all_lines() {
for (int i = 0; i < text.size(); i++) {
text.set_hidden(i, false);
@ -5262,151 +5236,14 @@ bool TextEdit::is_line_comment(int p_line) const {
return false;
}
bool TextEdit::can_fold(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), false);
if (!is_hiding_enabled()) {
return false;
}
if (p_line + 1 >= text.size()) {
return false;
}
if (text[p_line].strip_edges().size() == 0) {
return false;
}
if (is_folded(p_line)) {
return false;
}
if (is_line_hidden(p_line)) {
return false;
}
if (is_line_comment(p_line)) {
return false;
}
int start_indent = get_indent_level(p_line);
for (int i = p_line + 1; i < text.size(); i++) {
if (text[i].strip_edges().size() == 0) {
continue;
}
int next_indent = get_indent_level(i);
if (is_line_comment(i)) {
continue;
} else if (next_indent > start_indent) {
return true;
} else {
return false;
}
}
return false;
}
bool TextEdit::is_folded(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), false);
if (p_line + 1 >= text.size()) {
return false;
}
return !is_line_hidden(p_line) && is_line_hidden(p_line + 1);
}
Vector<int> TextEdit::get_folded_lines() const {
Vector<int> folded_lines;
for (int i = 0; i < text.size(); i++) {
if (is_folded(i)) {
folded_lines.push_back(i);
}
}
return folded_lines;
}
void TextEdit::fold_line(int p_line) {
ERR_FAIL_INDEX(p_line, text.size());
if (!is_hiding_enabled()) {
return;
}
if (!can_fold(p_line)) {
return;
}
// Hide lines below this one.
int start_indent = get_indent_level(p_line);
int last_line = start_indent;
for (int i = p_line + 1; i < text.size(); i++) {
if (text[i].strip_edges().size() != 0) {
if (is_line_comment(i)) {
continue;
} else if (get_indent_level(i) > start_indent) {
last_line = i;
} else {
break;
}
}
}
for (int i = p_line + 1; i <= last_line; i++) {
set_line_as_hidden(i, true);
}
// Fix selection.
if (is_selection_active()) {
if (is_line_hidden(selection.from_line) && is_line_hidden(selection.to_line)) {
deselect();
} else if (is_line_hidden(selection.from_line)) {
select(p_line, 9999, selection.to_line, selection.to_column);
} else if (is_line_hidden(selection.to_line)) {
select(selection.from_line, selection.from_column, p_line, 9999);
}
}
// Reset cursor.
if (is_line_hidden(cursor.line)) {
cursor_set_line(p_line, false, false);
cursor_set_column(get_line(p_line).length(), false);
}
_update_scrollbars();
update();
}
void TextEdit::unfold_line(int p_line) {
ERR_FAIL_INDEX(p_line, text.size());
if (!is_folded(p_line) && !is_line_hidden(p_line)) {
return;
}
int fold_start;
for (fold_start = p_line; fold_start > 0; fold_start--) {
if (is_folded(fold_start)) {
break;
}
}
fold_start = is_folded(fold_start) ? fold_start : p_line;
for (int i = fold_start + 1; i < text.size(); i++) {
if (is_line_hidden(i)) {
set_line_as_hidden(i, false);
} else {
break;
}
}
_update_scrollbars();
update();
}
void TextEdit::toggle_fold_line(int p_line) {
ERR_FAIL_INDEX(p_line, text.size());
if (!is_folded(p_line)) {
fold_line(p_line);
} else {
unfold_line(p_line);
}
}
int TextEdit::get_line_count() const {
return text.size();
}
int TextEdit::get_line_width(int p_line, int p_wrap_offset) const {
return text.get_line_width(p_line, p_wrap_offset);
}
void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) {
ERR_FAIL_COND(p_op.type == TextOperation::TYPE_NONE);
@ -6270,18 +6107,6 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_draw_spaces"), &TextEdit::set_draw_spaces);
ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces);
ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled);
ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled);
ClassDB::bind_method(D_METHOD("set_line_as_hidden", "line", "enable"), &TextEdit::set_line_as_hidden);
ClassDB::bind_method(D_METHOD("is_line_hidden", "line"), &TextEdit::is_line_hidden);
ClassDB::bind_method(D_METHOD("fold_all_lines"), &TextEdit::fold_all_lines);
ClassDB::bind_method(D_METHOD("unhide_all_lines"), &TextEdit::unhide_all_lines);
ClassDB::bind_method(D_METHOD("fold_line", "line"), &TextEdit::fold_line);
ClassDB::bind_method(D_METHOD("unfold_line", "line"), &TextEdit::unfold_line);
ClassDB::bind_method(D_METHOD("toggle_fold_line", "line"), &TextEdit::toggle_fold_line);
ClassDB::bind_method(D_METHOD("can_fold", "line"), &TextEdit::can_fold);
ClassDB::bind_method(D_METHOD("is_folded", "line"), &TextEdit::is_folded);
ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enable"), &TextEdit::set_highlight_all_occurrences);
ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled);
@ -6365,7 +6190,6 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "smooth_scrolling"), "set_smooth_scroll_enable", "is_smooth_scroll_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_scroll_speed"), "set_v_scroll_speed", "get_v_scroll_speed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hiding_enabled"), "set_hiding_enabled", "is_hiding_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_enabled"), "set_wrap_enabled", "is_wrap_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal"), "set_h_scroll", "get_h_scroll");

View file

@ -124,7 +124,7 @@ private:
void set_draw_control_chars(bool p_draw_control_chars);
int get_line_height(int p_line, int p_wrap_index) const;
int get_line_width(int p_line) const;
int get_line_width(int p_line, int p_wrap_index = -1) const;
int get_max_width(bool p_exclude_hidden = false) const;
void set_width(float p_width);
@ -349,11 +349,8 @@ private:
void update_cursor_wrap_offset();
void _update_wrap_at(bool p_force = false);
bool line_wraps(int line) const;
int times_line_wraps(int line) const;
Vector<String> get_wrap_rows_text(int p_line) const;
int get_cursor_wrap_index() const;
int get_line_wrap_index_at_col(int p_line, int p_column) const;
int get_char_count();
double get_scroll_pos_for_line(int p_line, int p_wrap_index = 0) const;
@ -514,6 +511,7 @@ public:
void set_gutter_width(int p_gutter, int p_width);
int get_gutter_width(int p_gutter) const;
int get_total_gutter_width() const;
void set_gutter_draw(int p_gutter, bool p_draw);
bool is_gutter_drawn(int p_gutter) const;
@ -622,24 +620,19 @@ public:
void insert_text_at_cursor(const String &p_text);
void insert_at(const String &p_text, int at);
int get_line_count() const;
int get_line_width(int p_line, int p_wrap_offset = -1) const;
int get_line_wrap_index_at_col(int p_line, int p_column) const;
void set_line_as_hidden(int p_line, bool p_hidden);
bool is_line_hidden(int p_line) const;
void fold_all_lines();
void unhide_all_lines();
int num_lines_from(int p_line_from, int visible_amount) const;
int num_lines_from_rows(int p_line_from, int p_wrap_index_from, int visible_amount, int &wrap_index) const;
int get_last_unhidden_line() const;
bool can_fold(int p_line) const;
bool is_folded(int p_line) const;
Vector<int> get_folded_lines() const;
void fold_line(int p_line);
void unfold_line(int p_line);
void toggle_fold_line(int p_line);
String get_text();
String get_line(int line) const;
bool has_ime_text() const;
void set_line(int line, String new_text);
int get_row_height() const;
void backspace_at_cursor();
@ -701,6 +694,8 @@ public:
void set_wrap_enabled(bool p_wrap_enabled);
bool is_wrap_enabled() const;
bool line_wraps(int line) const;
int times_line_wraps(int line) const;
void clear();

View file

@ -445,7 +445,6 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("font_readonly_color", "TextEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f));
theme->set_color("font_outline_color", "TextEdit", Color(1, 1, 1));
theme->set_color("selection_color", "TextEdit", control_selection_color);
theme->set_color("code_folding_color", "TextEdit", Color(0.8, 0.8, 0.8, 0.8));
theme->set_color("current_line_color", "TextEdit", Color(0.25, 0.25, 0.26, 0.8));
theme->set_color("caret_color", "TextEdit", control_font_color);
theme->set_color("caret_background_color", "TextEdit", Color(0, 0, 0));
@ -469,6 +468,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("executing_line", "CodeEdit", make_icon(arrow_right_png));
theme->set_icon("can_fold", "CodeEdit", make_icon(arrow_down_png));
theme->set_icon("folded", "CodeEdit", make_icon(arrow_right_png));
theme->set_icon("folded_eol_icon", "CodeEdit", make_icon(ellipsis_png));
theme->set_font("font", "CodeEdit", Ref<Font>());
theme->set_font_size("font_size", "CodeEdit", -1);
@ -487,8 +487,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("bookmark_color", "CodeEdit", Color(0.5, 0.64, 1, 0.8));
theme->set_color("breakpoint_color", "CodeEdit", Color(0.9, 0.29, 0.3));
theme->set_color("executing_line_color", "CodeEdit", Color(0.98, 0.89, 0.27));
theme->set_color("code_folding_color", "CodeEdit", Color(0.8, 0.8, 0.8, 0.8));
theme->set_color("current_line_color", "CodeEdit", Color(0.25, 0.25, 0.26, 0.8));
theme->set_color("code_folding_color", "CodeEdit", Color(0.8, 0.8, 0.8, 0.8));
theme->set_color("caret_color", "CodeEdit", control_font_color);
theme->set_color("caret_background_color", "CodeEdit", Color(0, 0, 0));
theme->set_color("brace_mismatch_color", "CodeEdit", Color(1, 0.2, 0.2));

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

View file

@ -74,6 +74,10 @@ static const unsigned char dropdown_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x8, 0x8, 0x4, 0x0, 0x0, 0x0, 0x6e, 0x6, 0x76, 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x60, 0xf8, 0xc0, 0xcc, 0x0, 0x2, 0x60, 0x16, 0x98, 0x78, 0x67, 0x8, 0x81, 0x6f, 0x4d, 0xde, 0x9a, 0x0, 0x5, 0xde, 0x3a, 0x3d, 0xfc, 0x8f, 0x80, 0xaf, 0xba, 0x18, 0xde, 0x29, 0x2, 0x19, 0xbf, 0x61, 0x2, 0x6f, 0x62, 0x18, 0x3e, 0xb0, 0xbd, 0x97, 0x4, 0x32, 0xff, 0x80, 0xb9, 0xb1, 0x20, 0x93, 0xc0, 0x42, 0x8, 0x2e, 0x54, 0xe8, 0x9d, 0xdc, 0x9b, 0x54, 0x10, 0xb, 0x21, 0xc4, 0x4, 0x63, 0x1, 0x0, 0x86, 0x1f, 0x3b, 0x1e, 0x92, 0x22, 0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const unsigned char ellipsis_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x8, 0x8, 0x6, 0x0, 0x0, 0x0, 0xc9, 0x11, 0xce, 0xcc, 0x0, 0x0, 0x0, 0x4, 0x73, 0x42, 0x49, 0x54, 0x8, 0x8, 0x8, 0x8, 0x7c, 0x8, 0x64, 0x88, 0x0, 0x0, 0x0, 0x78, 0x49, 0x44, 0x41, 0x54, 0x18, 0x95, 0x95, 0xd1, 0x31, 0xa, 0xc2, 0x50, 0x10, 0x4, 0xd0, 0xb7, 0x1f, 0xf, 0x60, 0x67, 0xa, 0xf, 0x12, 0x6f, 0x60, 0xe9, 0x51, 0x5, 0x3d, 0x44, 0xee, 0x61, 0xa1, 0xa5, 0xf6, 0x81, 0xb5, 0xc8, 0x47, 0x82, 0x84, 0x4f, 0xb2, 0xdd, 0xb0, 0x33, 0xb3, 0xb3, 0x4c, 0x40, 0x66, 0xee, 0x71, 0xc2, 0x1, 0x3b, 0xcb, 0x33, 0xe2, 0x85, 0x21, 0x22, 0xde, 0x51, 0x45, 0x97, 0x86, 0x60, 0xc9, 0xe0, 0x5a, 0xea, 0xa5, 0xb5, 0x22, 0x95, 0xdb, 0x97, 0x1a, 0xf, 0x6e, 0xb8, 0xcf, 0x8, 0x2d, 0xdc, 0x95, 0xd9, 0x22, 0xfe, 0x9c, 0x9b, 0x38, 0x32, 0xf3, 0x8c, 0xe3, 0x86, 0xa8, 0xf0, 0x28, 0x18, 0x4c, 0xf, 0xaf, 0x9d, 0x11, 0x43, 0xf0, 0xab, 0xa3, 0x47, 0xa7, 0x5d, 0xc7, 0xd3, 0x54, 0xc7, 0xe7, 0xb, 0xb9, 0xce, 0x1f, 0xc6, 0x2d, 0x99, 0x55, 0xc7, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const unsigned char error_icon_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xb, 0x13, 0x0, 0x0, 0xb, 0x13, 0x1, 0x0, 0x9a, 0x9c, 0x18, 0x0, 0x0, 0x0, 0x2, 0x62, 0x4b, 0x47, 0x44, 0x0, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x0, 0x0, 0x0, 0xe, 0x49, 0x44, 0x41, 0x54, 0x28, 0xcf, 0x63, 0x60, 0x18, 0x5, 0xa3, 0x0, 0x1, 0x0, 0x2, 0x10, 0x0, 0x1, 0x14, 0xc2, 0xc0, 0x92, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};