summaryrefslogtreecommitdiffstats
path: root/scene/gui/code_edit.cpp
diff options
context:
space:
mode:
authorRémi Verschelde <rverschelde@gmail.com>2024-04-30 17:03:04 +0200
committerRémi Verschelde <rverschelde@gmail.com>2024-04-30 17:03:04 +0200
commite19b808a7afa498921beee95ec15321ab29b735d (patch)
tree0924bc49b4ac76790316b07086338f358e763846 /scene/gui/code_edit.cpp
parentd9bf750cbf9b24103f85f43856e771fb739df4c8 (diff)
parent773a473807c75975b5820b30a4f0eccaece7cfa2 (diff)
downloadredot-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.cpp630
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;
}