|
@@ -641,11 +641,14 @@ void CodeEdit::_unhide_carets() {
|
|
|
// 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) : "");
|
|
@@ -703,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();
|
|
|
}
|
|
|
|
|
@@ -717,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.");
|
|
@@ -850,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();
|
|
|
}
|
|
|
|
|
@@ -866,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() {
|
|
@@ -919,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;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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);
|
|
|
+ remove_text(i, 0, i, spaces_to_remove);
|
|
|
}
|
|
|
}
|
|
|
- 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) {
|
|
@@ -1004,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);
|
|
@@ -1056,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);
|
|
|
|
|
@@ -1081,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);
|
|
|
}
|
|
|
}
|
|
@@ -1089,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 {
|
|
@@ -1119,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;
|
|
@@ -1150,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);
|
|
@@ -1195,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();
|
|
|
}
|
|
|
|
|
@@ -1712,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) {
|
|
@@ -1781,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);
|
|
@@ -1807,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 {
|
|
@@ -2248,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;
|
|
@@ -2282,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.
|
|
@@ -2299,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);
|
|
@@ -2325,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);
|
|
@@ -2341,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();
|
|
@@ -2430,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 */
|
|
@@ -2590,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);
|
|
@@ -2691,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 */
|