diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2024-04-30 17:03:04 +0200 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2024-04-30 17:03:04 +0200 |
commit | e19b808a7afa498921beee95ec15321ab29b735d (patch) | |
tree | 0924bc49b4ac76790316b07086338f358e763846 /scene/gui/code_edit.cpp | |
parent | d9bf750cbf9b24103f85f43856e771fb739df4c8 (diff) | |
parent | 773a473807c75975b5820b30a4f0eccaece7cfa2 (diff) | |
download | redot-engine-e19b808a7afa498921beee95ec15321ab29b735d.tar.gz |
Merge pull request #86978 from kitbdev/multicaret-overhaul
Overhaul multicaret editing and selection in TextEdit
Diffstat (limited to 'scene/gui/code_edit.cpp')
-rw-r--r-- | scene/gui/code_edit.cpp | 630 |
1 files changed, 312 insertions, 318 deletions
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 4f90504e35..8131fe7aaa 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -624,16 +624,31 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const { return TextEdit::get_cursor_shape(p_pos); } +void CodeEdit::_unhide_carets() { + // Unfold caret and selection origin. + for (int i = 0; i < get_caret_count(); i++) { + if (_is_line_hidden(get_caret_line(i))) { + unfold_line(get_caret_line(i)); + } + if (has_selection(i) && _is_line_hidden(get_selection_origin_line(i))) { + unfold_line(get_selection_origin_line(i)); + } + } +} + /* Text manipulation */ // Overridable actions void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) { start_action(EditAction::ACTION_TYPING); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } bool had_selection = has_selection(i); String selection_text = (had_selection ? get_selected_text(i) : ""); @@ -691,6 +706,7 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_ca insert_text_at_caret(chr, i); } } + end_multicaret_edit(); end_action(); } @@ -705,66 +721,80 @@ void CodeEdit::_backspace_internal(int p_caret) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { if (p_caret != -1 && p_caret != i) { continue; } + if (p_caret == -1 && multicaret_edit_ignore_caret(i)) { + continue; + } - int cc = get_caret_column(i); - int cl = get_caret_line(i); + int to_line = get_caret_line(i); + int to_column = get_caret_column(i); - if (cc == 0 && cl == 0) { + if (to_column == 0 && to_line == 0) { continue; } - if (cl > 0 && _is_line_hidden(cl - 1)) { - unfold_line(get_caret_line(i) - 1); + if (to_line > 0 && _is_line_hidden(to_line - 1)) { + unfold_line(to_line - 1); } - int prev_line = cc ? cl : cl - 1; - int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); + int from_line = to_column > 0 ? to_line : to_line - 1; + int from_column = to_column > 0 ? (to_column - 1) : (get_line(to_line - 1).length()); - merge_gutters(prev_line, cl); + merge_gutters(from_line, to_line); - if (auto_brace_completion_enabled && cc > 0) { - int idx = _get_auto_brace_pair_open_at_pos(cl, cc); + if (auto_brace_completion_enabled && to_column > 0) { + int idx = _get_auto_brace_pair_open_at_pos(to_line, to_column); if (idx != -1) { - prev_column = cc - auto_brace_completion_pairs[idx].open_key.length(); + from_column = to_column - auto_brace_completion_pairs[idx].open_key.length(); - if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { - cc += auto_brace_completion_pairs[idx].close_key.length(); + if (_get_auto_brace_pair_close_at_pos(to_line, to_column) == idx) { + to_column += auto_brace_completion_pairs[idx].close_key.length(); } - - remove_text(prev_line, prev_column, cl, cc); - - set_caret_line(prev_line, false, true, 0, i); - set_caret_column(prev_column, i == 0, i); - - adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); - continue; } } // For space indentation we need to do a basic unindent if there are no chars to the left, acting the same way as tabs. - if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) >= cc) { - prev_column = cc - _calculate_spaces_till_next_left_indent(cc); - prev_line = cl; + if (indent_using_spaces && to_column != 0) { + if (get_first_non_whitespace_column(to_line) >= to_column) { + from_column = to_column - _calculate_spaces_till_next_left_indent(to_column); + from_line = to_line; } } - remove_text(prev_line, prev_column, cl, cc); - - set_caret_line(prev_line, false, true, 0, i); - set_caret_column(prev_column, i == 0, i); + remove_text(from_line, from_column, to_line, to_column); - adjust_carets_after_edit(i, prev_line, prev_column, cl, cc); + set_caret_line(from_line, false, true, -1, i); + set_caret_column(from_column, i == 0, i); } - merge_overlapping_carets(); + + end_multicaret_edit(); end_complex_operation(); } +void CodeEdit::_cut_internal(int p_caret) { + // Overridden to unfold lines. + _copy_internal(p_caret); + + if (!is_editable()) { + return; + } + + if (has_selection(p_caret)) { + delete_selection(p_caret); + return; + } + if (p_caret == -1) { + delete_lines(); + } else { + unfold_line(get_caret_line(p_caret)); + remove_line_at(get_caret_line(p_caret)); + } +} + /* Indent management */ void CodeEdit::set_indent_size(const int p_size) { ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0."); @@ -838,13 +868,17 @@ void CodeEdit::do_indent() { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i)); if (spaces_to_add > 0) { insert_text_at_caret(String(" ").repeat(spaces_to_add), i); } } + end_multicaret_edit(); end_complex_operation(); } @@ -854,51 +888,28 @@ void CodeEdit::indent_lines() { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &c : caret_edit_order) { - // This value informs us by how much we changed selection position by indenting right. - // Default is 1 for tab indentation. - int selection_offset = 1; - - int start_line = get_caret_line(c); - int end_line = start_line; - if (has_selection(c)) { - start_line = get_selection_from_line(c); - end_line = get_selection_to_line(c); + begin_multicaret_edit(); - // Ignore the last line if the selection is not past the first column. - if (get_selection_to_column(c) == 0) { - selection_offset = 0; - end_line--; - } - } - - for (int i = start_line; i <= end_line; i++) { + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + for (int i = line_range.x; i <= line_range.y; i++) { const String line_text = get_line(i); - if (line_text.size() == 0 && has_selection(c)) { + if (line_text.size() == 0) { + // Ignore empty lines. continue; } - if (!indent_using_spaces) { - set_line(i, '\t' + line_text); - continue; + if (indent_using_spaces) { + int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); + insert_text(String(" ").repeat(spaces_to_add), i, 0, false); + } else { + insert_text("\t", i, 0, false); } - - // We don't really care where selection is - we just need to know indentation level at the beginning of the line. - // Since we will add this many spaces, we want to move the whole selection and caret by this much. - int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); - set_line(i, String(" ").repeat(spaces_to_add) + line_text); - selection_offset = spaces_to_add; } - - // Fix selection and caret being off after shifting selection right. - if (has_selection(c)) { - select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c); - } - set_caret_column(get_caret_column(c) + selection_offset, false, c); } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } void CodeEdit::unindent_lines() { @@ -907,76 +918,25 @@ void CodeEdit::unindent_lines() { } begin_complex_operation(); + begin_multicaret_edit(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &c : caret_edit_order) { - // Moving caret and selection after unindenting can get tricky because - // changing content of line can move caret and selection on its own (if new line ends before previous position of either) - // therefore we just remember initial values and at the end of the operation offset them by number of removed characters. - int removed_characters = 0; - int initial_selection_end_column = 0; - int initial_cursor_column = get_caret_column(c); - - int start_line = get_caret_line(c); - int end_line = start_line; - if (has_selection(c)) { - start_line = get_selection_from_line(c); - end_line = get_selection_to_line(c); - - // Ignore the last line if the selection is not past the first column. - initial_selection_end_column = get_selection_to_column(c); - if (initial_selection_end_column == 0) { - end_line--; - } - } - - bool first_line_edited = false; - bool last_line_edited = false; - - for (int i = start_line; i <= end_line; i++) { - String line_text = get_line(i); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + for (int i = line_range.x; i <= line_range.y; i++) { + const String line_text = get_line(i); if (line_text.begins_with("\t")) { - line_text = line_text.substr(1, line_text.length()); - - set_line(i, line_text); - removed_characters = 1; - - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; - continue; - } - - if (line_text.begins_with(" ")) { - // When unindenting we aim to remove spaces before line that has selection no matter what is selected. - // Here we remove only enough spaces to align text to nearest full multiple of indentation_size. - // In case where selection begins at the start of indentation_size multiple we remove whole indentation level. + remove_text(i, 0, i, 1); + } else if (line_text.begins_with(" ")) { + // Remove only enough spaces to align text to nearest full multiple of indentation_size. int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i)); - line_text = line_text.substr(spaces_to_remove, line_text.length()); - - set_line(i, line_text); - removed_characters = spaces_to_remove; - - first_line_edited = (i == start_line) ? true : first_line_edited; - last_line_edited = (i == end_line) ? true : last_line_edited; + remove_text(i, 0, i, spaces_to_remove); } } - - if (has_selection(c)) { - // Fix selection being off by one on the first line. - if (first_line_edited) { - select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c); - } - - // Fix selection being off by one on the last line. - if (last_line_edited) { - select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c); - } - } - set_caret_column(initial_cursor_column - removed_characters, false, c); } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } void CodeEdit::convert_indent(int p_from_line, int p_to_line) { @@ -992,27 +952,6 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { ERR_FAIL_COND(p_to_line >= get_line_count()); ERR_FAIL_COND(p_to_line < p_from_line); - // Store caret states. - Vector<int> caret_columns; - Vector<Pair<int, int>> from_selections; - Vector<Pair<int, int>> to_selections; - caret_columns.resize(get_caret_count()); - from_selections.resize(get_caret_count()); - to_selections.resize(get_caret_count()); - for (int c = 0; c < get_caret_count(); c++) { - caret_columns.write[c] = get_caret_column(c); - - // Set "selection_from_line" to -1 to allow checking if there was a selection later. - if (!has_selection(c)) { - from_selections.write[c].first = -1; - continue; - } - from_selections.write[c].first = get_selection_from_line(c); - from_selections.write[c].second = get_selection_from_column(c); - to_selections.write[c].first = get_selection_to_line(c); - to_selections.write[c].second = get_selection_to_column(c); - } - // Check lines within range. const char32_t from_indent_char = indent_using_spaces ? '\t' : ' '; int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1); @@ -1044,23 +983,10 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { line_changed = true; if (!changed_indentation) { begin_complex_operation(); + begin_multicaret_edit(); changed_indentation = true; } - // Calculate new caret state. - for (int c = 0; c < get_caret_count(); c++) { - if (get_caret_line(c) != i || caret_columns[c] <= j) { - continue; - } - caret_columns.write[c] += size_diff; - - if (from_selections.write[c].first == -1) { - continue; - } - from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second; - to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second; - } - // Calculate new line. line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1); @@ -1069,6 +995,7 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { } if (line_changed) { + // Use set line to preserve carets visual position. set_line(i, line); } } @@ -1077,16 +1004,9 @@ void CodeEdit::convert_indent(int p_from_line, int p_to_line) { return; } - // Restore caret states. - for (int c = 0; c < get_caret_count(); c++) { - set_caret_column(caret_columns[c], c == 0, c); - if (from_selections.write[c].first != -1) { - select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c); - } - } merge_overlapping_carets(); + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const { @@ -1107,15 +1027,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } // When not splitting the line, we need to factor in indentation from the end of the current line. const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length(); const int cl = get_caret_line(i); const String line = get_line(cl); - String ins = "\n"; + String ins = ""; + if (!p_above) { + ins = "\n"; + } // Append current indentation. int space_count = 0; @@ -1138,6 +1065,9 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } break; } + if (p_above) { + ins += "\n"; + } if (is_line_folded(cl)) { unfold_line(cl); @@ -1183,33 +1113,22 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { } } - bool first_line = false; - if (!p_split_current_line) { + if (p_split_current_line) { + insert_text_at_caret(ins, i); + } else { + insert_text(ins, cl, p_above ? 0 : get_line(cl).length(), p_above, p_above); deselect(i); - - if (p_above) { - if (cl > 0) { - set_caret_line(cl - 1, false, true, 0, i); - set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); - } else { - set_caret_column(0, i == 0, i); - first_line = true; - } - } else { - set_caret_column(line.length(), i == 0, i); - } + set_caret_line(p_above ? cl : cl + 1, false, true, -1, i); + set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); } - - insert_text_at_caret(ins, i); - - if (first_line) { - set_caret_line(0, i == 0, true, 0, i); - } else if (brace_indent) { + if (brace_indent) { + // Move to inner indented line. set_caret_line(get_caret_line(i) - 1, false, true, 0, i); set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i); } } + end_multicaret_edit(); end_complex_operation(); } @@ -1700,27 +1619,8 @@ void CodeEdit::fold_line(int p_line) { _set_line_as_hidden(i, true); } - for (int i = 0; i < get_caret_count(); i++) { - // Fix selection. - if (has_selection(i)) { - if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) { - deselect(i); - } else if (_is_line_hidden(get_selection_from_line(i))) { - select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i); - } else if (_is_line_hidden(get_selection_to_line(i))) { - select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i); - } - } - - // Reset caret. - if (_is_line_hidden(get_caret_line(i))) { - set_caret_line(p_line, false, false, 0, i); - set_caret_column(get_line(p_line).length(), false, i); - } - } - - merge_overlapping_carets(); - queue_redraw(); + // Collapse any carets in the hidden area. + collapse_carets(p_line, get_line(p_line).length(), end_line, get_line(end_line).length(), true); } void CodeEdit::unfold_line(int p_line) { @@ -1769,6 +1669,23 @@ void CodeEdit::toggle_foldable_line(int p_line) { fold_line(p_line); } +void CodeEdit::toggle_foldable_lines_at_carets() { + begin_multicaret_edit(); + int previous_line = -1; + Vector<int> sorted = get_sorted_carets(); + for (int caret_idx : sorted) { + if (multicaret_edit_ignore_caret(caret_idx)) { + continue; + } + int line_idx = get_caret_line(caret_idx); + if (line_idx != previous_line) { + toggle_foldable_line(line_idx); + previous_line = line_idx; + } + } + end_multicaret_edit(); +} + 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); @@ -1795,49 +1712,29 @@ void CodeEdit::create_code_region() { WARN_PRINT_ONCE("Cannot create code region without any one line comment delimiters"); return; } + String region_name = atr(ETR("New Code Region")); + begin_complex_operation(); - // Merge selections if selection starts on the same line the previous one ends. - Vector<int> caret_edit_order = get_caret_index_edit_order(); - Vector<int> carets_to_remove; - for (int i = 1; i < caret_edit_order.size(); i++) { - int current_caret = caret_edit_order[i - 1]; - int next_caret = caret_edit_order[i]; - if (get_selection_from_line(current_caret) == get_selection_to_line(next_caret)) { - select(get_selection_from_line(next_caret), get_selection_from_column(next_caret), get_selection_to_line(current_caret), get_selection_to_column(current_caret), next_caret); - carets_to_remove.append(current_caret); - } - } - // Sort and remove backwards to preserve indices. - carets_to_remove.sort(); - for (int i = carets_to_remove.size() - 1; i >= 0; i--) { - remove_caret(carets_to_remove[i]); - } - - // Adding start and end region tags. - int first_region_start = -1; - for (int caret_idx : get_caret_index_edit_order()) { - if (!has_selection(caret_idx)) { - continue; - } - int from_line = get_selection_from_line(caret_idx); - if (first_region_start == -1 || from_line < first_region_start) { - first_region_start = from_line; - } - int to_line = get_selection_to_line(caret_idx); - set_line(to_line, get_line(to_line) + "\n" + code_region_end_string); - insert_line_at(from_line, code_region_start_string + " " + atr(ETR("New Code Region"))); - fold_line(from_line); + begin_multicaret_edit(); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(true, false); + + // Add start and end region tags. + int line_offset = 0; + for (Point2i line_range : line_ranges) { + insert_text("\n" + code_region_end_string, line_range.y + line_offset, get_line(line_range.y + line_offset).length()); + insert_line_at(line_range.x + line_offset, code_region_start_string + " " + region_name); + fold_line(line_range.x + line_offset); + line_offset += 2; } + int first_region_start = line_ranges[0].x; // Select name of the first region to allow quick edit. remove_secondary_carets(); - set_caret_line(first_region_start); - int tag_length = code_region_start_string.length() + atr(ETR("New Code Region")).length() + 1; - set_caret_column(tag_length); + int tag_length = code_region_start_string.length() + region_name.length() + 1; select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length); + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } String CodeEdit::get_code_region_start_tag() const { @@ -2236,8 +2133,12 @@ void CodeEdit::confirm_code_completion(bool p_replace) { char32_t caret_last_completion_char = 0; begin_complex_operation(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &i : caret_edit_order) { + begin_multicaret_edit(); + + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } int caret_line = get_caret_line(i); const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; @@ -2270,8 +2171,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) { // Replace. remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col); - adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col); - set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); insert_text_at_caret(insert_text, i); } else { // Get first non-matching char. @@ -2287,8 +2186,6 @@ void CodeEdit::confirm_code_completion(bool p_replace) { // Remove base completion text. remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); - adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i)); - set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i); // Merge with text. insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i); @@ -2313,12 +2210,10 @@ void CodeEdit::confirm_code_completion(bool p_replace) { if (has_string_delimiter(String::chr(last_completion_char))) { if (post_brace_pair != -1 && last_char_matches) { remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); - adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); } } else { if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && last_char_matches) { remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); - adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1); } else if (auto_brace_completion_enabled && pre_brace_pair != -1) { insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i); set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i); @@ -2329,13 +2224,16 @@ void CodeEdit::confirm_code_completion(bool p_replace) { pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1); if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) { remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); - adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i)); - if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) { - set_caret_column(get_caret_column(i) - 1, i == 0, i); + if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) + 1) != pre_brace_pair) { + set_caret_column(get_caret_column(i) + 1, i == 0, i); + } else { + set_caret_column(get_caret_column(i) + 2, i == 0, i); } } } } + + end_multicaret_edit(); end_complex_operation(); cancel_code_completion(); @@ -2418,65 +2316,154 @@ void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) { } /* Text manipulation */ -void CodeEdit::duplicate_lines() { +void CodeEdit::move_lines_up() { begin_complex_operation(); + begin_multicaret_edit(); - Vector<int> caret_edit_order = get_caret_index_edit_order(); - for (const int &caret_index : caret_edit_order) { - // The text that will be inserted. All lines in one string. - String insert_text; - - // The new line position of the caret after the operation. - int new_caret_line = get_caret_line(caret_index); - // The new column position of the caret after the operation. - int new_caret_column = get_caret_column(caret_index); - // The caret positions of the selection. Stays -1 if there is no selection. - int select_from_line = -1; - int select_to_line = -1; - int select_from_column = -1; - int select_to_column = -1; - // Number of lines of the selection. - int select_num_lines = -1; - - if (has_selection(caret_index)) { - select_from_line = get_selection_from_line(caret_index); - select_to_line = get_selection_to_line(caret_index); - select_from_column = get_selection_from_column(caret_index); - select_to_column = get_selection_to_column(caret_index); - select_num_lines = select_to_line - select_from_line + 1; - - for (int i = select_from_line; i <= select_to_line; i++) { - insert_text += "\n" + get_line(i); - unfold_line(i); - } - new_caret_line = select_to_line + select_num_lines; - } else { - insert_text = "\n" + get_line(new_caret_line); - new_caret_line++; + // Move lines up by swapping each line with the one above it. + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + for (Point2i line_range : line_ranges) { + if (line_range.x == 0) { + continue; + } + unfold_line(line_range.x - 1); + for (int line = line_range.x; line <= line_range.y; line++) { + unfold_line(line); + swap_lines(line - 1, line); + } + } - unfold_line(get_caret_line(caret_index)); + // Fix selection if it ended at column 0, since it wasn't moved. + for (int i = 0; i < get_caret_count(); i++) { + if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != 0) { + if (is_caret_after_selection_origin(i)) { + set_caret_line(get_caret_line(i) - 1, false, true, -1, i); + } else { + set_selection_origin_line(get_selection_origin_line(i) - 1, true, -1, i); + } } + } - // The text will be inserted at the end of the current line. - set_caret_column(get_line(get_caret_line(caret_index)).length(), false, caret_index); + end_multicaret_edit(); + end_complex_operation(); +} - deselect(caret_index); +void CodeEdit::move_lines_down() { + begin_complex_operation(); + begin_multicaret_edit(); - insert_text_at_caret(insert_text, caret_index); - set_caret_line(new_caret_line, false, true, 0, caret_index); - set_caret_column(new_caret_column, true, caret_index); + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); - if (select_from_line != -1) { - // Advance the selection by the number of duplicated lines. - select_from_line += select_num_lines; - select_to_line += select_num_lines; + // Fix selection if it ended at column 0, since it won't be moved. + for (int i = 0; i < get_caret_count(); i++) { + if (has_selection(i) && get_selection_to_column(i) == 0 && get_selection_to_line(i) != get_line_count() - 1) { + if (is_caret_after_selection_origin(i)) { + set_caret_line(get_caret_line(i) + 1, false, true, -1, i); + } else { + set_selection_origin_line(get_selection_origin_line(i) + 1, true, -1, i); + } + } + } - select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index); + // Move lines down by swapping each line with the one below it. + for (Point2i line_range : line_ranges) { + if (line_range.y == get_line_count() - 1) { + continue; + } + unfold_line(line_range.y + 1); + for (int line = line_range.y; line >= line_range.x; line--) { + unfold_line(line); + swap_lines(line + 1, line); } } + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::delete_lines() { + begin_complex_operation(); + begin_multicaret_edit(); + + Vector<Point2i> line_ranges = get_line_ranges_from_carets(); + int line_offset = 0; + for (Point2i line_range : line_ranges) { + // Remove last line of range separately to preserve carets. + unfold_line(line_range.y + line_offset); + remove_line_at(line_range.y + line_offset); + if (line_range.x != line_range.y) { + remove_text(line_range.x + line_offset, 0, line_range.y + line_offset, 0); + } + line_offset += line_range.x - line_range.y - 1; + } + + // Deselect all. + deselect(); + + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::duplicate_selection() { + begin_complex_operation(); + begin_multicaret_edit(); + + // Duplicate lines from carets without selections first. + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } + for (int l = get_selection_from_line(i); l <= get_selection_to_line(i); l++) { + unfold_line(l); + } + if (has_selection(i)) { + continue; + } + + String text_to_insert = get_line(get_caret_line(i)) + "\n"; + // Insert new text before the line, so the caret is on the second one. + insert_text(text_to_insert, get_caret_line(i), 0); + } + + // Duplicate selections. + for (int i = 0; i < get_caret_count(); i++) { + if (multicaret_edit_ignore_caret(i)) { + continue; + } + if (!has_selection(i)) { + continue; + } + + // Insert new text before the selection, so the caret is on the second one. + insert_text(get_selected_text(i), get_selection_from_line(i), get_selection_from_column(i)); + } + + end_multicaret_edit(); + end_complex_operation(); +} + +void CodeEdit::duplicate_lines() { + begin_complex_operation(); + begin_multicaret_edit(); + + Vector<Point2i> line_ranges = get_line_ranges_from_carets(false, false); + int line_offset = 0; + for (Point2i line_range : line_ranges) { + // The text that will be inserted. All lines in one string. + String text_to_insert; + + for (int i = line_range.x + line_offset; i <= line_range.y + line_offset; i++) { + text_to_insert += get_line(i) + "\n"; + unfold_line(i); + } + + // Insert new text before the line. + insert_text(text_to_insert, line_range.x + line_offset, 0); + line_offset += line_range.y - line_range.x + 1; + } + + end_multicaret_edit(); end_complex_operation(); - queue_redraw(); } /* Visual */ @@ -2578,6 +2565,7 @@ void CodeEdit::_bind_methods() { 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("toggle_foldable_lines_at_carets"), &CodeEdit::toggle_foldable_lines_at_carets); 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); @@ -2679,6 +2667,10 @@ void CodeEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid); /* Text manipulation */ + ClassDB::bind_method(D_METHOD("move_lines_up"), &CodeEdit::move_lines_up); + ClassDB::bind_method(D_METHOD("move_lines_down"), &CodeEdit::move_lines_down); + ClassDB::bind_method(D_METHOD("delete_lines"), &CodeEdit::delete_lines); + ClassDB::bind_method(D_METHOD("duplicate_selection"), &CodeEdit::duplicate_selection); ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines); /* Inspector */ @@ -2846,10 +2838,12 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) { if (p_gutter == line_number_gutter) { remove_secondary_carets(); - set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); - select(p_line, 0, p_line + 1, 0); - set_caret_line(p_line + 1); - set_caret_column(0); + set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE); + if (p_line == get_line_count() - 1) { + select(p_line, 0, p_line, INT_MAX); + } else { + select(p_line, 0, p_line + 1, 0); + } return; } |