You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2026-01-05 19:31:35 +00:00
Overhaul multiple caret editing in TextEdit.
Use a multicaret edit to delay merging overlapping carets until the end.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
int cc = get_caret_column(i);
|
||||
int cl = get_caret_line(i);
|
||||
|
||||
if (cc == 0 && cl == 0) {
|
||||
if (p_caret == -1 && multicaret_edit_ignore_caret(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cl > 0 && _is_line_hidden(cl - 1)) {
|
||||
unfold_line(get_caret_line(i) - 1);
|
||||
int to_line = get_caret_line(i);
|
||||
int to_column = get_caret_column(i);
|
||||
|
||||
if (to_column == 0 && to_line == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int prev_line = cc ? cl : cl - 1;
|
||||
int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
|
||||
if (to_line > 0 && _is_line_hidden(to_line - 1)) {
|
||||
unfold_line(to_line - 1);
|
||||
}
|
||||
|
||||
merge_gutters(prev_line, cl);
|
||||
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());
|
||||
|
||||
if (auto_brace_completion_enabled && cc > 0) {
|
||||
int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
|
||||
merge_gutters(from_line, to_line);
|
||||
|
||||
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);
|
||||
remove_text(from_line, from_column, to_line, to_column);
|
||||
|
||||
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);
|
||||
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;
|
||||
begin_multicaret_edit();
|
||||
|
||||
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.
|
||||
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;
|
||||
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) {
|
||||
@@ -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;
|
||||
}
|
||||
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]);
|
||||
}
|
||||
String region_name = atr(ETR("New Code Region"));
|
||||
|
||||
// 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_complex_operation();
|
||||
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++;
|
||||
|
||||
unfold_line(get_caret_line(caret_index));
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
deselect(caret_index);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
select(select_from_line, select_from_column, select_to_line, select_to_column, caret_index);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end_multicaret_edit();
|
||||
end_complex_operation();
|
||||
}
|
||||
|
||||
void CodeEdit::move_lines_down() {
|
||||
begin_complex_operation();
|
||||
begin_multicaret_edit();
|
||||
|
||||
Vector<Point2i> line_ranges = get_line_ranges_from_carets();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 */
|
||||
|
||||
Reference in New Issue
Block a user