diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 7074ba9e890..e6c161d5f92 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -391,6 +391,13 @@ Returns [code]true[/code] if the given line is folded. See [method fold_line]. + + + + + Joins all selected lines or lines containing a caret with their next line. Whitespace in between will be removed. If the next line has content, the [param line_ending] will be inserted in between. + + diff --git a/editor/gui/code_editor.cpp b/editor/gui/code_editor.cpp index e56bd0a04c9..c84c85560a6 100644 --- a/editor/gui/code_editor.cpp +++ b/editor/gui/code_editor.cpp @@ -910,6 +910,11 @@ void CodeTextEditor::input(const Ref &event) { accept_event(); return; } + if (ED_IS_SHORTCUT("script_text_editor/join_lines", key_event)) { + text_editor->join_lines(); + accept_event(); + return; + } if (ED_IS_SHORTCUT("script_text_editor/duplicate_selection", key_event)) { text_editor->duplicate_selection(); accept_event(); diff --git a/editor/script/script_editor_base.cpp b/editor/script/script_editor_base.cpp index 5a8403036bd..f609b182a73 100644 --- a/editor/script/script_editor_base.cpp +++ b/editor/script/script_editor_base.cpp @@ -200,6 +200,7 @@ TextEditorBase::EditMenus::EditMenus() { edit_menu_line->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); edit_menu_line->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); edit_menu_line->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + edit_menu_line->add_shortcut(ED_GET_SHORTCUT("script_text_editor/join_lines"), EDIT_JOIN_LINES); edit_menu_line->connect(SceneStringName(id_pressed), callable_mp(this, &EditMenus::_edit_option)); edit_menu->get_popup()->add_submenu_node_item(TTRC("Line"), edit_menu_line); } @@ -412,6 +413,9 @@ bool TextEditorBase::_edit_option(int p_op) { case EDIT_DELETE_LINE: { tx->delete_lines(); } break; + case EDIT_JOIN_LINES: { + tx->join_lines(); + } break; case EDIT_DUPLICATE_SELECTION: { tx->duplicate_selection(); } break; diff --git a/editor/script/script_editor_base.h b/editor/script/script_editor_base.h index 1fdf97c7f6c..c008d63af52 100644 --- a/editor/script/script_editor_base.h +++ b/editor/script/script_editor_base.h @@ -119,6 +119,8 @@ protected: BOOKMARK_GOTO_PREV, BOOKMARK_REMOVE_ALL, + EDIT_JOIN_LINES, + BASE_ENUM_COUNT, }; diff --git a/editor/script/script_text_editor.cpp b/editor/script/script_text_editor.cpp index 20c0f6ce69d..7af31439f52 100644 --- a/editor/script/script_text_editor.cpp +++ b/editor/script/script_text_editor.cpp @@ -2528,6 +2528,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/move_up", TTRC("Move Up"), KeyModifierMask::ALT | Key::UP); ED_SHORTCUT("script_text_editor/move_down", TTRC("Move Down"), KeyModifierMask::ALT | Key::DOWN); ED_SHORTCUT("script_text_editor/delete_line", TTRC("Delete Line"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::K); + ED_SHORTCUT("script_text_editor/join_lines", TTRC("Join Lines"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::J); // Leave these at zero, same can be accomplished with tab/shift-tab, including selection. // The next/previous in history shortcut in this case makes a lot more sense. diff --git a/editor/shader/text_shader_editor.cpp b/editor/shader/text_shader_editor.cpp index 41e9d7a87d5..29ce0de284f 100644 --- a/editor/shader/text_shader_editor.cpp +++ b/editor/shader/text_shader_editor.cpp @@ -779,6 +779,9 @@ void TextShaderEditor::_menu_option(int p_option) { case EDIT_EMOJI_AND_SYMBOL: { code_editor->get_text_editor()->show_emoji_and_symbol_picker(); } break; + case EDIT_JOIN_LINES: { + code_editor->get_text_editor()->join_lines(); + } break; } if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(false); @@ -1228,6 +1231,7 @@ TextShaderEditor::TextShaderEditor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/join_lines"), EDIT_JOIN_LINES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES); diff --git a/editor/shader/text_shader_editor.h b/editor/shader/text_shader_editor.h index 80a6c83c4b3..891a669000e 100644 --- a/editor/shader/text_shader_editor.h +++ b/editor/shader/text_shader_editor.h @@ -134,6 +134,7 @@ class TextShaderEditor : public ShaderEditor { BOOKMARK_REMOVE_ALL, HELP_DOCS, EDIT_EMOJI_AND_SYMBOL, + EDIT_JOIN_LINES, }; HBoxContainer *menu_bar_hbox = nullptr; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 79569421277..3aca6ebde74 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -2699,6 +2699,44 @@ void CodeEdit::delete_lines() { end_complex_operation(); } +void CodeEdit::join_lines(const String &p_line_ending) { + ERR_FAIL_COND_MSG(p_line_ending.contains_char('\n'), "Cannot join lines with a newline."); + + begin_complex_operation(); + begin_multicaret_edit(); + + Vector line_ranges = get_line_ranges_from_carets(); + int line_offset = 0; + for (const Point2i &line_range : line_ranges) { + for (int32_t line_index = line_range.x; line_index <= line_range.y; line_index++) { + int32_t real_line = line_index + line_offset; + if (real_line + 1 >= get_line_count()) { + break; + } + unfold_line(real_line); + String line = get_line(real_line); + int line_length = line.length(); + int next_line_leading_whitespace_length = get_first_non_whitespace_column(real_line + 1); + int next_line_length = get_line(real_line + 1).length(); + int corrected_line_length = line_length - 1; + for (; corrected_line_length >= 0; corrected_line_length--) { + if (!is_whitespace(line[corrected_line_length])) { + break; + } + } + corrected_line_length++; + remove_text(real_line, corrected_line_length, real_line + 1, next_line_leading_whitespace_length); + if (next_line_leading_whitespace_length != next_line_length && corrected_line_length != 0) { + insert_text(p_line_ending, real_line, corrected_line_length); + } + line_offset--; + } + } + + end_multicaret_edit(); + end_complex_operation(); +} + void CodeEdit::duplicate_selection() { begin_complex_operation(); begin_multicaret_edit(); @@ -2971,6 +3009,7 @@ void CodeEdit::_bind_methods() { 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("join_lines", "line_ending"), &CodeEdit::join_lines, DEFVAL(" ")); ClassDB::bind_method(D_METHOD("duplicate_selection"), &CodeEdit::duplicate_selection); ClassDB::bind_method(D_METHOD("duplicate_lines"), &CodeEdit::duplicate_lines); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index bda57af261c..4cf9ca9feb3 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -523,6 +523,7 @@ public: void move_lines_up(); void move_lines_down(); void delete_lines(); + void join_lines(const String &p_line_ending = " "); void duplicate_selection(); void duplicate_lines();