From 0cbe176ce635cb0b313d7ea21d287a6f82fb5fed Mon Sep 17 00:00:00 2001 From: Paulb23 Date: Wed, 8 Jun 2022 22:41:38 +0100 Subject: [PATCH] Add multi caret support to Editor --- editor/code_editor.cpp | 674 +++++++++++++++++--------- editor/code_editor.h | 2 +- editor/plugins/script_text_editor.cpp | 27 +- editor/plugins/text_editor.cpp | 7 +- editor/plugins/text_shader_editor.cpp | 1 + 5 files changed, 456 insertions(+), 255 deletions(-) diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 11a6912aa5d..aa291887a2f 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -55,6 +55,7 @@ void GotoLineDialog::ok_pressed() { if (get_line() < 1 || get_line() > text_editor->get_line_count()) { return; } + text_editor->remove_secondary_carets(); text_editor->unfold_line(get_line() - 1); text_editor->set_caret_line(get_line() - 1); hide(); @@ -149,7 +150,7 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) text_editor->unfold_line(pos.y); text_editor->set_caret_line(pos.y, false); text_editor->set_caret_column(pos.x + text.length(), false); - text_editor->center_viewport_to_caret(); + text_editor->center_viewport_to_caret(0); text_editor->select(pos.y, pos.x, pos.y, pos.x + text.length()); line_col_changed_for_result = true; @@ -176,11 +177,11 @@ bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) } void FindReplaceBar::_replace() { - bool selection_enabled = text_editor->has_selection(); + bool selection_enabled = text_editor->has_selection(0); Point2i selection_begin, selection_end; if (selection_enabled) { - selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_column()); - selection_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column()); + selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0)); + selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0)); } String replace_text = get_replace_text(); @@ -188,25 +189,25 @@ void FindReplaceBar::_replace() { text_editor->begin_complex_operation(); if (selection_enabled && is_selection_only()) { // To restrict search_current() to selected region - text_editor->set_caret_line(selection_begin.width); - text_editor->set_caret_column(selection_begin.height); + text_editor->set_caret_line(selection_begin.width, false, true, 0, 0); + text_editor->set_caret_column(selection_begin.height, true, 0); } if (search_current()) { text_editor->unfold_line(result_line); - text_editor->select(result_line, result_col, result_line, result_col + search_text_len); + text_editor->select(result_line, result_col, result_line, result_col + search_text_len, 0); if (selection_enabled && is_selection_only()) { Point2i match_from(result_line, result_col); Point2i match_to(result_line, result_col + search_text_len); if (!(match_from < selection_begin || match_to > selection_end)) { - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); if (match_to.x == selection_end.x) { // Adjust selection bounds if necessary selection_end.y += replace_text.length() - search_text_len; } } } else { - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); } } text_editor->end_complex_operation(); @@ -216,29 +217,29 @@ void FindReplaceBar::_replace() { if (selection_enabled && is_selection_only()) { // Reselect in order to keep 'Replace' restricted to selection - text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y); + text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y, 0); } else { - text_editor->deselect(); + text_editor->deselect(0); } } void FindReplaceBar::_replace_all() { text_editor->disconnect("text_changed", callable_mp(this, &FindReplaceBar::_editor_text_changed)); // Line as x so it gets priority in comparison, column as y. - Point2i orig_cursor(text_editor->get_caret_line(), text_editor->get_caret_column()); + Point2i orig_cursor(text_editor->get_caret_line(0), text_editor->get_caret_column(0)); Point2i prev_match = Point2(-1, -1); - bool selection_enabled = text_editor->has_selection(); + bool selection_enabled = text_editor->has_selection(0); Point2i selection_begin, selection_end; if (selection_enabled) { - selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_column()); - selection_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column()); + selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0)); + selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0)); } int vsval = text_editor->get_v_scroll(); - text_editor->set_caret_line(0); - text_editor->set_caret_column(0); + text_editor->set_caret_line(0, false, true, 0, 0); + text_editor->set_caret_column(0, true, 0); String replace_text = get_replace_text(); int search_text_len = get_search_text().length(); @@ -250,8 +251,8 @@ void FindReplaceBar::_replace_all() { text_editor->begin_complex_operation(); if (selection_enabled && is_selection_only()) { - text_editor->set_caret_line(selection_begin.width); - text_editor->set_caret_column(selection_begin.height); + text_editor->set_caret_line(selection_begin.width, false, true, 0, 0); + text_editor->set_caret_column(selection_begin.height, true, 0); } if (search_current()) { do { @@ -266,7 +267,7 @@ void FindReplaceBar::_replace_all() { prev_match = Point2i(result_line, result_col + replace_text.length()); text_editor->unfold_line(result_line); - text_editor->select(result_line, result_col, result_line, match_to.y); + text_editor->select(result_line, result_col, result_line, match_to.y, 0); if (selection_enabled && is_selection_only()) { if (match_from < selection_begin || match_to > selection_end) { @@ -274,14 +275,14 @@ void FindReplaceBar::_replace_all() { } // Replace but adjust selection bounds. - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); if (match_to.x == selection_end.x) { selection_end.y += replace_text.length() - search_text_len; } } else { // Just replace. - text_editor->insert_text_at_caret(replace_text); + text_editor->insert_text_at_caret(replace_text, 0); } rc++; @@ -293,14 +294,14 @@ void FindReplaceBar::_replace_all() { replace_all_mode = false; // Restore editor state (selection, cursor, scroll). - text_editor->set_caret_line(orig_cursor.x); - text_editor->set_caret_column(orig_cursor.y); + text_editor->set_caret_line(orig_cursor.x, false, true, 0, 0); + text_editor->set_caret_column(orig_cursor.y, true, 0); if (selection_enabled && is_selection_only()) { // Reselect. - text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y); + text_editor->select(selection_begin.x, selection_begin.y, selection_end.x, selection_end.y, 0); } else { - text_editor->deselect(); + text_editor->deselect(0); } text_editor->set_v_scroll(vsval); @@ -314,10 +315,10 @@ void FindReplaceBar::_replace_all() { } void FindReplaceBar::_get_search_from(int &r_line, int &r_col) { - r_line = text_editor->get_caret_line(); - r_col = text_editor->get_caret_column(); + r_line = text_editor->get_caret_line(0); + r_col = text_editor->get_caret_column(0); - if (text_editor->has_selection() && is_selection_only()) { + if (text_editor->has_selection(0) && is_selection_only()) { return; } @@ -434,7 +435,7 @@ bool FindReplaceBar::search_prev() { int line, col; _get_search_from(line, col); - if (text_editor->has_selection()) { + if (text_editor->has_selection(0)) { col--; // Skip currently selected word. } @@ -512,8 +513,8 @@ void FindReplaceBar::_show_search(bool p_focus_replace, bool p_show_only) { search_text->call_deferred(SNAME("grab_focus")); } - if (text_editor->has_selection() && !selection_only->is_pressed()) { - search_text->set_text(text_editor->get_selected_text()); + if (text_editor->has_selection(0) && !selection_only->is_pressed()) { + search_text->set_text(text_editor->get_selected_text(0)); } if (!get_search_text().is_empty()) { @@ -548,9 +549,9 @@ void FindReplaceBar::popup_replace() { hbc_option_replace->show(); } - selection_only->set_pressed((text_editor->has_selection() && text_editor->get_selection_from_line() < text_editor->get_selection_to_line())); + selection_only->set_pressed((text_editor->has_selection(0) && text_editor->get_selection_from_line(0) < text_editor->get_selection_to_line(0))); - _show_search(is_visible() || text_editor->has_selection()); + _show_search(is_visible() || text_editor->has_selection(0)); } void FindReplaceBar::_search_options_changed(bool p_pressed) { @@ -587,7 +588,7 @@ void FindReplaceBar::_search_text_submitted(const String &p_text) { } void FindReplaceBar::_replace_text_submitted(const String &p_text) { - if (selection_only->is_pressed() && text_editor->has_selection()) { + if (selection_only->is_pressed() && text_editor->has_selection(0)) { _replace_all(); _hide_bar(); } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { @@ -1091,6 +1092,7 @@ void CodeTextEditor::trim_trailing_whitespace() { } if (trimed_whitespace) { + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1122,8 +1124,11 @@ void CodeTextEditor::convert_indent_to_spaces() { indent += " "; } - int cursor_line = text_editor->get_caret_line(); - int cursor_column = text_editor->get_caret_column(); + Vector cursor_columns; + cursor_columns.resize(text_editor->get_caret_count()); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + cursor_columns.write[c] = text_editor->get_caret_column(c); + } bool changed_indentation = false; for (int i = 0; i < text_editor->get_line_count(); i++) { @@ -1140,8 +1145,10 @@ void CodeTextEditor::convert_indent_to_spaces() { text_editor->begin_complex_operation(); changed_indentation = true; } - if (cursor_line == i && cursor_column > j) { - cursor_column += indent_size - 1; + for (int c = 0; c < text_editor->get_caret_count(); c++) { + if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) { + cursor_columns.write[c] += indent_size - 1; + } } line = line.left(j) + indent + line.substr(j + 1); } @@ -1152,7 +1159,10 @@ void CodeTextEditor::convert_indent_to_spaces() { } } if (changed_indentation) { - text_editor->set_caret_column(cursor_column); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + text_editor->set_caret_column(cursor_columns[c], c == 0, c); + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1162,8 +1172,11 @@ void CodeTextEditor::convert_indent_to_tabs() { int indent_size = EditorSettings::get_singleton()->get("text_editor/behavior/indent/size"); indent_size -= 1; - int cursor_line = text_editor->get_caret_line(); - int cursor_column = text_editor->get_caret_column(); + Vector cursor_columns; + cursor_columns.resize(text_editor->get_caret_count()); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + cursor_columns.write[c] = text_editor->get_caret_column(c); + } bool changed_indentation = false; for (int i = 0; i < text_editor->get_line_count(); i++) { @@ -1184,8 +1197,10 @@ void CodeTextEditor::convert_indent_to_tabs() { text_editor->begin_complex_operation(); changed_indentation = true; } - if (cursor_line == i && cursor_column > j) { - cursor_column -= indent_size; + for (int c = 0; c < text_editor->get_caret_count(); c++) { + if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) { + cursor_columns.write[c] -= indent_size; + } } line = line.left(j - indent_size) + "\t" + line.substr(j + 1); j = 0; @@ -1201,7 +1216,10 @@ void CodeTextEditor::convert_indent_to_tabs() { } } if (changed_indentation) { - text_editor->set_caret_column(cursor_column); + for (int c = 0; c < text_editor->get_caret_count(); c++) { + text_editor->set_caret_column(cursor_columns[c], c == 0, c); + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } @@ -1211,59 +1229,128 @@ void CodeTextEditor::convert_case(CaseStyle p_case) { if (!text_editor->has_selection()) { return; } - text_editor->begin_complex_operation(); - int begin = text_editor->get_selection_from_line(); - int end = text_editor->get_selection_to_line(); - int begin_col = text_editor->get_selection_from_column(); - int end_col = text_editor->get_selection_to_column(); - - for (int i = begin; i <= end; i++) { - int len = text_editor->get_line(i).length(); - if (i == end) { - len = end_col; - } - if (i == begin) { - len -= begin_col; - } - String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); - - switch (p_case) { - case UPPER: { - new_line = new_line.to_upper(); - } break; - case LOWER: { - new_line = new_line.to_lower(); - } break; - case CAPITALIZE: { - new_line = new_line.capitalize(); - } break; + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + if (!text_editor->has_selection(c)) { + continue; } - if (i == begin) { - new_line = text_editor->get_line(i).left(begin_col) + new_line; + int begin = text_editor->get_selection_from_line(c); + int end = text_editor->get_selection_to_line(c); + int begin_col = text_editor->get_selection_from_column(c); + int end_col = text_editor->get_selection_to_column(c); + + for (int i = begin; i <= end; i++) { + int len = text_editor->get_line(i).length(); + if (i == end) { + len = end_col; + } + if (i == begin) { + len -= begin_col; + } + String new_line = text_editor->get_line(i).substr(i == begin ? begin_col : 0, len); + + switch (p_case) { + case UPPER: { + new_line = new_line.to_upper(); + } break; + case LOWER: { + new_line = new_line.to_lower(); + } break; + case CAPITALIZE: { + new_line = new_line.capitalize(); + } break; + } + + if (i == begin) { + new_line = text_editor->get_line(i).left(begin_col) + new_line; + } + if (i == end) { + new_line = new_line + text_editor->get_line(i).substr(end_col); + } + text_editor->set_line(i, new_line); } - if (i == end) { - new_line = new_line + text_editor->get_line(i).substr(end_col); - } - text_editor->set_line(i, new_line); } text_editor->end_complex_operation(); } void CodeTextEditor::move_lines_up() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int from_line = text_editor->get_selection_from_line(); - int from_col = text_editor->get_selection_from_column(); - int to_line = text_editor->get_selection_to_line(); - int to_column = text_editor->get_selection_to_column(); - int cursor_line = text_editor->get_caret_line(); - for (int i = from_line; i <= to_line; i++) { - int line_id = i; - int next_id = i - 1; + Vector carets_to_remove; + + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; + } + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int from_line = text_editor->get_selection_from_line(c); + int from_col = text_editor->get_selection_from_column(c); + int to_line = text_editor->get_selection_to_line(c); + int to_column = text_editor->get_selection_to_column(c); + int cursor_line = text_editor->get_caret_line(c); + + for (int j = from_line; j <= to_line; j++) { + int line_id = j; + int next_id = j - 1; + + if (line_id == 0 || next_id < 0) { + return; + } + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); + } + int from_line_up = from_line > 0 ? from_line - 1 : from_line; + int to_line_up = to_line > 0 ? to_line - 1 : to_line; + int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line; + text_editor->select(from_line_up, from_col, to_line_up, to_column, c); + text_editor->set_caret_line(cursor_line_up, c == 0, true, 0, c); + } else { + int line_id = text_editor->get_caret_line(c); + int next_id = line_id - 1; if (line_id == 0 || next_id < 0) { return; @@ -1273,238 +1360,336 @@ void CodeTextEditor::move_lines_up() { text_editor->unfold_line(next_id); text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); } - int from_line_up = from_line > 0 ? from_line - 1 : from_line; - int to_line_up = to_line > 0 ? to_line - 1 : to_line; - int cursor_line_up = cursor_line > 0 ? cursor_line - 1 : cursor_line; - text_editor->select(from_line_up, from_col, to_line_up, to_column); - text_editor->set_caret_line(cursor_line_up); - } else { - int line_id = text_editor->get_caret_line(); - int next_id = line_id - 1; - - if (line_id == 0 || next_id < 0) { - return; - } - - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); - - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); } text_editor->end_complex_operation(); + text_editor->merge_overlapping_carets(); text_editor->queue_redraw(); } void CodeTextEditor::move_lines_down() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int from_line = text_editor->get_selection_from_line(); - int from_col = text_editor->get_selection_from_column(); - int to_line = text_editor->get_selection_to_line(); - int to_column = text_editor->get_selection_to_column(); - int cursor_line = text_editor->get_caret_line(); - for (int i = to_line; i >= from_line; i--) { - int line_id = i; - int next_id = i + 1; + Vector carets_to_remove; + + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; + } + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int from_line = text_editor->get_selection_from_line(c); + int from_col = text_editor->get_selection_from_column(c); + int to_line = text_editor->get_selection_to_line(c); + int to_column = text_editor->get_selection_to_column(c); + int cursor_line = text_editor->get_caret_line(c); + + for (int l = to_line; l >= from_line; l--) { + int line_id = l; + int next_id = l + 1; + + if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { + continue; + } + + text_editor->unfold_line(line_id); + text_editor->unfold_line(next_id); + + text_editor->swap_lines(line_id, next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); + } + int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; + int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; + int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line; + text_editor->select(from_line_down, from_col, to_line_down, to_column, c); + text_editor->set_caret_line(cursor_line_down, c == 0, true, 0, c); + } else { + int line_id = text_editor->get_caret_line(c); + int next_id = line_id + 1; if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - return; + continue; } text_editor->unfold_line(line_id); text_editor->unfold_line(next_id); text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); + text_editor->set_caret_line(next_id, c == 0, true, 0, c); } - int from_line_down = from_line < text_editor->get_line_count() ? from_line + 1 : from_line; - int to_line_down = to_line < text_editor->get_line_count() ? to_line + 1 : to_line; - int cursor_line_down = cursor_line < text_editor->get_line_count() ? cursor_line + 1 : cursor_line; - text_editor->select(from_line_down, from_col, to_line_down, to_column); - text_editor->set_caret_line(cursor_line_down); - } else { - int line_id = text_editor->get_caret_line(); - int next_id = line_id + 1; - - if (line_id == text_editor->get_line_count() - 1 || next_id > text_editor->get_line_count()) { - return; - } - - text_editor->unfold_line(line_id); - text_editor->unfold_line(next_id); - - text_editor->swap_lines(line_id, next_id); - text_editor->set_caret_line(next_id); } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + text_editor->remove_caret(carets_to_remove[i]); + } + + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } -void CodeTextEditor::_delete_line(int p_line) { +void CodeTextEditor::_delete_line(int p_line, int p_caret) { // this is currently intended to be called within delete_lines() // so `begin_complex_operation` is omitted here text_editor->set_line(p_line, ""); if (p_line == 0 && text_editor->get_line_count() > 1) { - text_editor->set_caret_line(1); - text_editor->set_caret_column(0); + text_editor->set_caret_line(1, p_caret == 0, true, 0, p_caret); + text_editor->set_caret_column(0, p_caret == 0, p_caret); } - text_editor->backspace(); + text_editor->backspace(p_caret); if (p_line < text_editor->get_line_count()) { text_editor->unfold_line(p_line); } - text_editor->set_caret_line(p_line); + text_editor->set_caret_line(p_line, p_caret == 0, true, 0, p_caret); } void CodeTextEditor::delete_lines() { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int to_line = text_editor->get_selection_to_line(); - int from_line = text_editor->get_selection_from_line(); - int count = Math::abs(to_line - from_line) + 1; - text_editor->set_caret_line(from_line, false); - text_editor->deselect(); - for (int i = 0; i < count; i++) { - _delete_line(from_line); + Vector carets_to_remove; + + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (int i = 0; i < caret_edit_order.size(); i++) { + int c = caret_edit_order[i]; + int cl = text_editor->get_caret_line(c); + + bool swaped_caret = false; + for (int j = i + 1; j < caret_edit_order.size(); j++) { + if (text_editor->has_selection(caret_edit_order[j])) { + if (text_editor->get_selection_from_line() == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + continue; + } + + if (text_editor->get_selection_to_line() == cl) { + if (text_editor->has_selection(c)) { + if (text_editor->get_selection_to_line(c) != cl) { + text_editor->select(cl + 1, 0, text_editor->get_selection_to_line(c), text_editor->get_selection_to_column(c), c); + break; + } + } + + carets_to_remove.push_back(c); + i = j - 1; + swaped_caret = true; + break; + } + break; + } + + if (text_editor->get_caret_line(caret_edit_order[j]) == cl) { + carets_to_remove.push_back(caret_edit_order[j]); + i = j; + continue; + } + break; + } + + if (swaped_caret) { + continue; + } + + if (text_editor->has_selection(c)) { + int to_line = text_editor->get_selection_to_line(c); + int from_line = text_editor->get_selection_from_line(c); + int count = Math::abs(to_line - from_line) + 1; + + text_editor->set_caret_line(from_line, false, true, 0, c); + text_editor->deselect(c); + for (int j = 0; j < count; j++) { + _delete_line(from_line, c); + } + } else { + _delete_line(text_editor->get_caret_line(c), c); } - } else { - _delete_line(text_editor->get_caret_line()); } + + // Sort and remove backwards to preserve indexes. + carets_to_remove.sort(); + for (int i = carets_to_remove.size() - 1; i >= 0; i--) { + text_editor->remove_caret(carets_to_remove[i]); + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); } void CodeTextEditor::duplicate_selection() { - const int cursor_column = text_editor->get_caret_column(); - int from_line = text_editor->get_caret_line(); - int to_line = text_editor->get_caret_line(); - int from_column = 0; - int to_column = 0; - int cursor_new_line = to_line + 1; - int cursor_new_column = text_editor->get_caret_column(); - String new_text = "\n" + text_editor->get_line(from_line); - bool selection_active = false; - - text_editor->set_caret_column(text_editor->get_line(from_line).length()); - if (text_editor->has_selection()) { - from_column = text_editor->get_selection_from_column(); - to_column = text_editor->get_selection_to_column(); - - from_line = text_editor->get_selection_from_line(); - to_line = text_editor->get_selection_to_line(); - cursor_new_line = to_line + text_editor->get_caret_line() - from_line; - cursor_new_column = to_column == cursor_column ? 2 * to_column - from_column : to_column; - new_text = text_editor->get_selected_text(); - selection_active = true; - - text_editor->set_caret_line(to_line); - text_editor->set_caret_column(to_column); - } - text_editor->begin_complex_operation(); - for (int i = from_line; i <= to_line; i++) { - text_editor->unfold_line(i); - } - text_editor->deselect(); - text_editor->insert_text_at_caret(new_text); - text_editor->set_caret_line(cursor_new_line); - text_editor->set_caret_column(cursor_new_column); - if (selection_active) { - text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column); - } + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + const int cursor_column = text_editor->get_caret_column(c); + int from_line = text_editor->get_caret_line(c); + int to_line = text_editor->get_caret_line(c); + int from_column = 0; + int to_column = 0; + int cursor_new_line = to_line + 1; + int cursor_new_column = text_editor->get_caret_column(c); + String new_text = "\n" + text_editor->get_line(from_line); + bool selection_active = false; + text_editor->set_caret_column(text_editor->get_line(from_line).length(), c == 0, c); + if (text_editor->has_selection(c)) { + from_column = text_editor->get_selection_from_column(c); + to_column = text_editor->get_selection_to_column(c); + + from_line = text_editor->get_selection_from_line(c); + to_line = text_editor->get_selection_to_line(c); + cursor_new_line = to_line + text_editor->get_caret_line(c) - from_line; + cursor_new_column = to_column == cursor_column ? 2 * to_column - from_column : to_column; + new_text = text_editor->get_selected_text(c); + selection_active = true; + + text_editor->set_caret_line(to_line, c == 0, true, 0, c); + text_editor->set_caret_column(to_column, c == 0, c); + } + + for (int i = from_line; i <= to_line; i++) { + text_editor->unfold_line(i); + } + text_editor->deselect(c); + text_editor->insert_text_at_caret(new_text, c); + text_editor->set_caret_line(cursor_new_line, c == 0, true, 0, c); + text_editor->set_caret_column(cursor_new_column, c == 0, c); + if (selection_active) { + text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column, c); + } + } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } void CodeTextEditor::toggle_inline_comment(const String &delimiter) { text_editor->begin_complex_operation(); - if (text_editor->has_selection()) { - int begin = text_editor->get_selection_from_line(); - int end = text_editor->get_selection_to_line(); - // End of selection ends on the first column of the last line, ignore it. - if (text_editor->get_selection_to_column() == 0) { - end -= 1; - } + Vector caret_edit_order = text_editor->get_caret_index_edit_order(); + for (const int &c : caret_edit_order) { + if (text_editor->has_selection(c)) { + int begin = text_editor->get_selection_from_line(c); + int end = text_editor->get_selection_to_line(c); - int col_to = text_editor->get_selection_to_column(); - int cursor_pos = text_editor->get_caret_column(); - - // Check if all lines in the selected block are commented. - bool is_commented = true; - for (int i = begin; i <= end; i++) { - if (!text_editor->get_line(i).begins_with(delimiter)) { - is_commented = false; - break; + // End of selection ends on the first column of the last line, ignore it. + if (text_editor->get_selection_to_column(c) == 0) { + end -= 1; } - } - for (int i = begin; i <= end; i++) { - String line_text = text_editor->get_line(i); - if (line_text.strip_edges().is_empty()) { - line_text = delimiter; - } else { - if (is_commented) { - line_text = line_text.substr(delimiter.length(), line_text.length()); - } else { - line_text = delimiter + line_text; + int col_to = text_editor->get_selection_to_column(c); + int cursor_pos = text_editor->get_caret_column(c); + + // Check if all lines in the selected block are commented. + bool is_commented = true; + for (int i = begin; i <= end; i++) { + if (!text_editor->get_line(i).begins_with(delimiter)) { + is_commented = false; + break; } } - text_editor->set_line(i, line_text); - } + for (int i = begin; i <= end; i++) { + String line_text = text_editor->get_line(i); - // Adjust selection & cursor position. - int offset = (is_commented ? -1 : 1) * delimiter.length(); - int col_from = text_editor->get_selection_from_column() > 0 ? text_editor->get_selection_from_column() + offset : 0; + if (line_text.strip_edges().is_empty()) { + line_text = delimiter; + } else { + if (is_commented) { + line_text = line_text.substr(delimiter.length(), line_text.length()); + } else { + line_text = delimiter + line_text; + } + } + text_editor->set_line(i, line_text); + } - if (is_commented && text_editor->get_caret_column() == text_editor->get_line(text_editor->get_caret_line()).length() + 1) { - cursor_pos += 1; - } + // Adjust selection & cursor position. + int offset = (is_commented ? -1 : 1) * delimiter.length(); + int col_from = text_editor->get_selection_from_column(c) > 0 ? text_editor->get_selection_from_column(c) + offset : 0; - if (text_editor->get_selection_to_column() != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line()).length() + 1) { - col_to += offset; - } + if (is_commented && text_editor->get_caret_column(c) == text_editor->get_line(text_editor->get_caret_line(c)).length() + 1) { + cursor_pos += 1; + } - if (text_editor->get_caret_column() != 0) { - cursor_pos += offset; - } + if (text_editor->get_selection_to_column(c) != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line(c)).length() + 1) { + col_to += offset; + } - text_editor->select(begin, col_from, text_editor->get_selection_to_line(), col_to); - text_editor->set_caret_column(cursor_pos); + if (text_editor->get_caret_column(c) != 0) { + cursor_pos += offset; + } - } else { - int begin = text_editor->get_caret_line(); - String line_text = text_editor->get_line(begin); - int delimiter_length = delimiter.length(); + text_editor->select(begin, col_from, text_editor->get_selection_to_line(c), col_to, c); + text_editor->set_caret_column(cursor_pos, c == 0, c); - int col = text_editor->get_caret_column(); - if (line_text.begins_with(delimiter)) { - line_text = line_text.substr(delimiter_length, line_text.length()); - col -= delimiter_length; } else { - line_text = delimiter + line_text; - col += delimiter_length; - } + int begin = text_editor->get_caret_line(c); + String line_text = text_editor->get_line(begin); + int delimiter_length = delimiter.length(); - text_editor->set_line(begin, line_text); - text_editor->set_caret_column(col); + int col = text_editor->get_caret_column(c); + if (line_text.begins_with(delimiter)) { + line_text = line_text.substr(delimiter_length, line_text.length()); + col -= delimiter_length; + } else { + line_text = delimiter + line_text; + col += delimiter_length; + } + + text_editor->set_line(begin, line_text); + text_editor->set_caret_column(col, c == 0, c); + } } + text_editor->merge_overlapping_carets(); text_editor->end_complex_operation(); text_editor->queue_redraw(); } void CodeTextEditor::goto_line(int p_line) { + text_editor->remove_secondary_carets(); text_editor->deselect(); text_editor->unfold_line(p_line); text_editor->call_deferred(SNAME("set_caret_line"), p_line); } void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { + text_editor->remove_secondary_carets(); text_editor->unfold_line(p_line); text_editor->call_deferred(SNAME("set_caret_line"), p_line); text_editor->call_deferred(SNAME("set_caret_column"), p_begin); @@ -1608,6 +1793,7 @@ void CodeTextEditor::goto_error() { if (text_editor->get_line_count() != error_line) { text_editor->unfold_line(error_line); } + text_editor->remove_secondary_carets(); text_editor->set_caret_line(error_line); text_editor->set_caret_column(error_column); text_editor->center_viewport_to_caret(); @@ -1784,8 +1970,10 @@ void CodeTextEditor::set_warning_count(int p_warning_count) { } void CodeTextEditor::toggle_bookmark() { - int line = text_editor->get_caret_line(); - text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line)); + for (int i = 0; i < text_editor->get_caret_count(); i++) { + int line = text_editor->get_caret_line(i); + text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line)); + } } void CodeTextEditor::goto_next_bookmark() { @@ -1794,6 +1982,7 @@ void CodeTextEditor::goto_next_bookmark() { return; } + text_editor->remove_secondary_carets(); int line = text_editor->get_caret_line(); if (line >= (int)bmarks[bmarks.size() - 1]) { text_editor->unfold_line(bmarks[0]); @@ -1818,6 +2007,7 @@ void CodeTextEditor::goto_prev_bookmark() { return; } + text_editor->remove_secondary_carets(); int line = text_editor->get_caret_line(); if (line <= (int)bmarks[0]) { text_editor->unfold_line(bmarks[bmarks.size() - 1]); diff --git a/editor/code_editor.h b/editor/code_editor.h index 49679cc7004..709a120c1e0 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -197,7 +197,7 @@ class CodeTextEditor : public VBoxContainer { void _update_status_bar_theme(); - void _delete_line(int p_line); + void _delete_line(int p_line, int p_caret); void _toggle_scripts_pressed(); protected: diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index a1d24907e51..95e28ba1c9c 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -267,6 +267,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { void ScriptTextEditor::_error_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { + code_editor->get_text_editor()->remove_secondary_carets(); code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); } } @@ -295,6 +296,7 @@ void ScriptTextEditor::reload_text() { void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) { String code = code_editor->get_text_editor()->get_text(); int pos = script->get_language()->find_function(p_function, code); + code_editor->get_text_editor()->remove_secondary_carets(); if (pos == -1) { //does not exist code_editor->get_text_editor()->deselect(); @@ -1363,6 +1365,7 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } + tx->remove_secondary_carets(); int line = tx->get_caret_line(); // wrap around @@ -1389,6 +1392,7 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } + tx->remove_secondary_carets(); int line = tx->get_caret_line(); // wrap around if (line <= (int)bpoints[0]) { @@ -1409,21 +1413,21 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case HELP_CONTEXTUAL: { - String text = tx->get_selected_text(); + String text = tx->get_selected_text(0); if (text.is_empty()) { - text = tx->get_word_under_caret(); + text = tx->get_word_under_caret(0); } if (!text.is_empty()) { emit_signal(SNAME("request_help"), text); } } break; case LOOKUP_SYMBOL: { - String text = tx->get_word_under_caret(); + String text = tx->get_word_under_caret(0); if (text.is_empty()) { - text = tx->get_selected_text(); + text = tx->get_selected_text(0); } if (!text.is_empty()) { - _lookup_symbol(text, tx->get_caret_line(), tx->get_caret_column()); + _lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0)); } } break; } @@ -1601,6 +1605,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data int col = pos.x; if (d.has("type") && String(d["type"]) == "resource") { + te->remove_secondary_carets(); Ref res = d["resource"]; if (!res.is_valid()) { return; @@ -1618,6 +1623,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) { + te->remove_secondary_carets(); Array files = d["files"]; String text_to_drop; @@ -1641,6 +1647,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && String(d["type"]) == "nodes") { + te->remove_secondary_carets(); Node *scene_root = get_tree()->get_edited_scene_root(); if (!scene_root) { EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene.")); @@ -1725,6 +1732,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && String(d["type"]) == "obj_property") { + te->remove_secondary_carets(); const String text_to_drop = String(d["property"]).c_escape().quote(quote_style); te->set_caret_line(row); @@ -1745,8 +1753,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; } else if (k.is_valid() && k->is_action("ui_menu", true)) { - tx->adjust_viewport_to_caret(); - local_pos = tx->get_caret_draw_pos(); + tx->adjust_viewport_to_caret(0); + local_pos = tx->get_caret_draw_pos(0); create_menu = true; } @@ -1757,6 +1765,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -1776,10 +1785,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { String word_at_pos = tx->get_word_at_pos(local_pos); if (word_at_pos.is_empty()) { - word_at_pos = tx->get_word_under_caret(); + word_at_pos = tx->get_word_under_caret(0); } if (word_at_pos.is_empty()) { - word_at_pos = tx->get_selected_text(); + word_at_pos = tx->get_selected_text(0); } bool has_color = (word_at_pos == "Color"); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 9846cd4a849..b17ee4c115a 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -441,6 +441,7 @@ void TextEditor::_text_edit_gui_input(const Ref &ev) { bool is_folded = tx->is_line_folded(row); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line(); @@ -467,9 +468,9 @@ void TextEditor::_text_edit_gui_input(const Ref &ev) { Ref k = ev; if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { CodeEdit *tx = code_editor->get_text_editor(); - int line = tx->get_caret_line(); - tx->adjust_viewport_to_caret(); - _make_context_menu(tx->has_selection(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos())); + int line = tx->get_caret_line(0); + tx->adjust_viewport_to_caret(0); + _make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0))); context_menu->grab_focus(); } } diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index dfd5e76ba6e..ecbe1c078f4 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -954,6 +954,7 @@ void TextShaderEditor::_text_edit_gui_input(const Ref &ev) { tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); if (tx->is_move_caret_on_right_click_enabled()) { + tx->remove_secondary_carets(); if (tx->has_selection()) { int from_line = tx->get_selection_from_line(); int to_line = tx->get_selection_to_line();