Merge pull request #61902 from Paulb23/multi-caret

Add Multi-caret support to TextEdit
This commit is contained in:
Rémi Verschelde 2022-10-06 08:56:34 +02:00
commit f5903215d0
11 changed files with 3812 additions and 2100 deletions

View file

@ -5,6 +5,7 @@
</brief_description> </brief_description>
<description> <description>
TextEdit is meant for editing large, multiline text. It also has facilities for editing code, such as syntax highlighting support and multiple levels of undo/redo. TextEdit is meant for editing large, multiline text. It also has facilities for editing code, such as syntax highlighting support and multiple levels of undo/redo.
[b]Note:[/b] Most viewport, caret and edit methods contain a [code]caret_index[/code] argument for [member caret_multiple] support. The argument should be one of the following: [code]-1[/code] for all carets, [code]0[/code] for the main caret, or greater than [code]0[/code] for secondary carets.
[b]Note:[/b] When holding down [kbd]Alt[/kbd], the vertical scroll wheel will scroll 5 times as fast as it would normally do. This also works in the Godot script editor. [b]Note:[/b] When holding down [kbd]Alt[/kbd], the vertical scroll wheel will scroll 5 times as fast as it would normally do. This also works in the Godot script editor.
</description> </description>
<tutorials> <tutorials>
@ -12,18 +13,21 @@
<methods> <methods>
<method name="_backspace" qualifiers="virtual"> <method name="_backspace" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user presses the backspace key. Override this method to define what happens when the user presses the backspace key.
</description> </description>
</method> </method>
<method name="_copy" qualifiers="virtual"> <method name="_copy" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user performs a copy operation. Override this method to define what happens when the user performs a copy operation.
</description> </description>
</method> </method>
<method name="_cut" qualifiers="virtual"> <method name="_cut" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user performs a cut operation. Override this method to define what happens when the user performs a cut operation.
</description> </description>
@ -31,23 +35,34 @@
<method name="_handle_unicode_input" qualifiers="virtual"> <method name="_handle_unicode_input" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="unicode_char" type="int" /> <param index="0" name="unicode_char" type="int" />
<param index="1" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user types in the provided key [param unicode_char]. Override this method to define what happens when the user types in the provided key [param unicode_char].
</description> </description>
</method> </method>
<method name="_paste" qualifiers="virtual"> <method name="_paste" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user performs a paste operation. Override this method to define what happens when the user performs a paste operation.
</description> </description>
</method> </method>
<method name="_paste_primary_clipboard" qualifiers="virtual"> <method name="_paste_primary_clipboard" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" />
<description> <description>
Override this method to define what happens when the user performs a paste operation with middle mouse button. Override this method to define what happens when the user performs a paste operation with middle mouse button.
[b]Note:[/b] This method is only implemented on Linux. [b]Note:[/b] This method is only implemented on Linux.
</description> </description>
</method> </method>
<method name="add_caret">
<return type="int" />
<param index="0" name="line" type="int" />
<param index="1" name="col" type="int" />
<description>
Adds a new caret at the given location. Returns the index of the new caret, or [code]-1[/code] if the location is invalid.
</description>
</method>
<method name="add_gutter"> <method name="add_gutter">
<return type="void" /> <return type="void" />
<param index="0" name="at" type="int" default="-1" /> <param index="0" name="at" type="int" default="-1" />
@ -55,14 +70,27 @@
Register a new gutter to this [TextEdit]. Use [param at] to have a specific gutter order. A value of [code]-1[/code] appends the gutter to the right. Register a new gutter to this [TextEdit]. Use [param at] to have a specific gutter order. A value of [code]-1[/code] appends the gutter to the right.
</description> </description>
</method> </method>
<method name="adjust_carets_after_edit">
<return type="void" />
<param index="0" name="caret" type="int" />
<param index="1" name="from_line" type="int" />
<param index="2" name="from_col" type="int" />
<param index="3" name="to_line" type="int" />
<param index="4" name="to_col" type="int" />
<description>
Reposition the carets affected by the edit. This assumes edits are applied in edit order, see [method get_caret_index_edit_order].
</description>
</method>
<method name="adjust_viewport_to_caret"> <method name="adjust_viewport_to_caret">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Adjust the viewport so the caret is visible. Adjust the viewport so the caret is visible.
</description> </description>
</method> </method>
<method name="backspace"> <method name="backspace">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Called when the user presses the backspace key. Can be overridden with [method _backspace]. Called when the user presses the backspace key. Can be overridden with [method _backspace].
</description> </description>
@ -75,6 +103,7 @@
</method> </method>
<method name="center_viewport_to_caret"> <method name="center_viewport_to_caret">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Centers the viewport on the line the editing caret is at. This also resets the [member scroll_horizontal] value to [code]0[/code]. Centers the viewport on the line the editing caret is at. This also resets the [member scroll_horizontal] value to [code]0[/code].
</description> </description>
@ -93,28 +122,38 @@
</method> </method>
<method name="copy"> <method name="copy">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Copies the current text selection. Can be overridden with [method _copy]. Copies the current text selection. Can be overridden with [method _copy].
</description> </description>
</method> </method>
<method name="cut"> <method name="cut">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Cut's the current selection. Can be overridden with [method _cut]. Cut's the current selection. Can be overridden with [method _cut].
</description> </description>
</method> </method>
<method name="delete_selection"> <method name="delete_selection">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Deletes the selected text. Deletes the selected text.
</description> </description>
</method> </method>
<method name="deselect"> <method name="deselect">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Deselects the current selection. Deselects the current selection.
</description> </description>
</method> </method>
<method name="end_action">
<return type="void" />
<description>
Marks the end of steps in the current action started with [method start_action].
</description>
</method>
<method name="end_complex_operation"> <method name="end_complex_operation">
<return type="void" /> <return type="void" />
<description> <description>
@ -123,24 +162,40 @@
</method> </method>
<method name="get_caret_column" qualifiers="const"> <method name="get_caret_column" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the column the editing caret is at. Returns the column the editing caret is at.
</description> </description>
</method> </method>
<method name="get_caret_count" qualifiers="const">
<return type="int" />
<description>
Returns the number of carets in this [TextEdit].
</description>
</method>
<method name="get_caret_draw_pos" qualifiers="const"> <method name="get_caret_draw_pos" qualifiers="const">
<return type="Vector2" /> <return type="Vector2" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the caret pixel draw position. Returns the caret pixel draw position.
</description> </description>
</method> </method>
<method name="get_caret_index_edit_order">
<return type="PackedInt32Array" />
<description>
Returns a list of caret indexes in their edit order, this done from bottom to top. Edit order refers to the way actions such as [method insert_text_at_caret] are applied.
</description>
</method>
<method name="get_caret_line" qualifiers="const"> <method name="get_caret_line" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the line the editing caret is on. Returns the line the editing caret is on.
</description> </description>
</method> </method>
<method name="get_caret_wrap_index" qualifiers="const"> <method name="get_caret_wrap_index" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the wrap index the editing caret is on. Returns the wrap index the editing caret is on.
</description> </description>
@ -381,32 +436,37 @@
Returns the scroll position for [param wrap_index] of [param line]. Returns the scroll position for [param wrap_index] of [param line].
</description> </description>
</method> </method>
<method name="get_selected_text" qualifiers="const"> <method name="get_selected_text">
<return type="String" /> <return type="String" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Returns the text inside the selection. Returns the text inside the selection.
</description> </description>
</method> </method>
<method name="get_selection_column" qualifiers="const"> <method name="get_selection_column" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the original start column of the selection. Returns the original start column of the selection.
</description> </description>
</method> </method>
<method name="get_selection_from_column" qualifiers="const"> <method name="get_selection_from_column" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the selection begin column. Returns the selection begin column.
</description> </description>
</method> </method>
<method name="get_selection_from_line" qualifiers="const"> <method name="get_selection_from_line" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the selection begin line. Returns the selection begin line.
</description> </description>
</method> </method>
<method name="get_selection_line" qualifiers="const"> <method name="get_selection_line" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the original start line of the selection. Returns the original start line of the selection.
</description> </description>
@ -419,12 +479,14 @@
</method> </method>
<method name="get_selection_to_column" qualifiers="const"> <method name="get_selection_to_column" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the selection end column. Returns the selection end column.
</description> </description>
</method> </method>
<method name="get_selection_to_line" qualifiers="const"> <method name="get_selection_to_line" qualifiers="const">
<return type="int" /> <return type="int" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns the selection end line. Returns the selection end line.
</description> </description>
@ -476,6 +538,7 @@
</method> </method>
<method name="get_word_under_caret" qualifiers="const"> <method name="get_word_under_caret" qualifiers="const">
<return type="String" /> <return type="String" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Returns a [String] text with the word under the caret's location. Returns a [String] text with the word under the caret's location.
</description> </description>
@ -494,6 +557,7 @@
</method> </method>
<method name="has_selection" qualifiers="const"> <method name="has_selection" qualifiers="const">
<return type="bool" /> <return type="bool" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Returns [code]true[/code] if the user has selected text. Returns [code]true[/code] if the user has selected text.
</description> </description>
@ -515,12 +579,14 @@
<method name="insert_text_at_caret"> <method name="insert_text_at_caret">
<return type="void" /> <return type="void" />
<param index="0" name="text" type="String" /> <param index="0" name="text" type="String" />
<param index="1" name="caret_index" type="int" default="-1" />
<description> <description>
Insert the specified text at the caret position. Insert the specified text at the caret position.
</description> </description>
</method> </method>
<method name="is_caret_visible" qualifiers="const"> <method name="is_caret_visible" qualifiers="const">
<return type="bool" /> <return type="bool" />
<param index="0" name="caret_index" type="int" default="0" />
<description> <description>
Returns [code]true[/code] if the caret is visible on the screen. Returns [code]true[/code] if the caret is visible on the screen.
</description> </description>
@ -576,6 +642,7 @@
<method name="is_mouse_over_selection" qualifiers="const"> <method name="is_mouse_over_selection" qualifiers="const">
<return type="bool" /> <return type="bool" />
<param index="0" name="edges" type="bool" /> <param index="0" name="edges" type="bool" />
<param index="1" name="caret_index" type="int" default="-1" />
<description> <description>
Returns whether the mouse is over selection. If [param edges] is [code]true[/code], the edges are considered part of the selection. Returns whether the mouse is over selection. If [param edges] is [code]true[/code], the edges are considered part of the selection.
</description> </description>
@ -601,18 +668,41 @@
Merge the gutters from [param from_line] into [param to_line]. Only overwritable gutters will be copied. Merge the gutters from [param from_line] into [param to_line]. Only overwritable gutters will be copied.
</description> </description>
</method> </method>
<method name="paste"> <method name="merge_overlapping_carets">
<return type="void" /> <return type="void" />
<description>
Merges any overlapping carets. Will favour the newest caret, or the caret with a selection.
[b]Note:[/b] This is not called when a caret changes position but after certain actions, so it is possible to get into a state where carets overlap.
</description>
</method>
<method name="paste">
<return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Paste at the current location. Can be overridden with [method _paste]. Paste at the current location. Can be overridden with [method _paste].
</description> </description>
</method> </method>
<method name="paste_primary_clipboard">
<return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description>
Pastes the primary clipboard.
</description>
</method>
<method name="redo"> <method name="redo">
<return type="void" /> <return type="void" />
<description> <description>
Perform redo operation. Perform redo operation.
</description> </description>
</method> </method>
<method name="remove_caret">
<return type="void" />
<param index="0" name="caret" type="int" />
<description>
Removes the given caret index.
[b]Note:[/b] This can result in adjustment of all other caret indices.
</description>
</method>
<method name="remove_gutter"> <method name="remove_gutter">
<return type="void" /> <return type="void" />
<param index="0" name="gutter" type="int" /> <param index="0" name="gutter" type="int" />
@ -620,6 +710,12 @@
Removes the gutter from this [TextEdit]. Removes the gutter from this [TextEdit].
</description> </description>
</method> </method>
<method name="remove_secondary_carets">
<return type="void" />
<description>
Removes all additional carets.
</description>
</method>
<method name="remove_text"> <method name="remove_text">
<return type="void" /> <return type="void" />
<param index="0" name="from_line" type="int" /> <param index="0" name="from_line" type="int" />
@ -666,6 +762,7 @@
<param index="1" name="from_column" type="int" /> <param index="1" name="from_column" type="int" />
<param index="2" name="to_line" type="int" /> <param index="2" name="to_line" type="int" />
<param index="3" name="to_column" type="int" /> <param index="3" name="to_column" type="int" />
<param index="4" name="caret_index" type="int" default="0" />
<description> <description>
Perform selection, from line/column to line/column. Perform selection, from line/column to line/column.
If [member selecting_enabled] is [code]false[/code], no selection will occur. If [member selecting_enabled] is [code]false[/code], no selection will occur.
@ -680,6 +777,7 @@
</method> </method>
<method name="select_word_under_caret"> <method name="select_word_under_caret">
<return type="void" /> <return type="void" />
<param index="0" name="caret_index" type="int" default="-1" />
<description> <description>
Selects the word under the caret. Selects the word under the caret.
</description> </description>
@ -688,9 +786,11 @@
<return type="void" /> <return type="void" />
<param index="0" name="column" type="int" /> <param index="0" name="column" type="int" />
<param index="1" name="adjust_viewport" type="bool" default="true" /> <param index="1" name="adjust_viewport" type="bool" default="true" />
<param index="2" name="caret_index" type="int" default="0" />
<description> <description>
Moves the caret to the specified [param column] index. Moves the caret to the specified [param column] index.
If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs. If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs.
[b]Note:[/b] If supporting multiple carets this will not check for any overlap. See [method merge_overlapping_carets].
</description> </description>
</method> </method>
<method name="set_caret_line"> <method name="set_caret_line">
@ -699,10 +799,12 @@
<param index="1" name="adjust_viewport" type="bool" default="true" /> <param index="1" name="adjust_viewport" type="bool" default="true" />
<param index="2" name="can_be_hidden" type="bool" default="true" /> <param index="2" name="can_be_hidden" type="bool" default="true" />
<param index="3" name="wrap_index" type="int" default="0" /> <param index="3" name="wrap_index" type="int" default="0" />
<param index="4" name="caret_index" type="int" default="0" />
<description> <description>
Moves the caret to the specified [param line] index. Moves the caret to the specified [param line] index.
If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs. If [param adjust_viewport] is [code]true[/code], the viewport will center at the caret position after the move occurs.
If [param can_be_hidden] is [code]true[/code], the specified [code]line[/code] can be hidden. If [param can_be_hidden] is [code]true[/code], the specified [code]line[/code] can be hidden.
[b]Note:[/b] If supporting multiple carets this will not check for any overlap. See [method merge_overlapping_carets].
</description> </description>
</method> </method>
<method name="set_gutter_clickable"> <method name="set_gutter_clickable">
@ -872,6 +974,7 @@
<param index="0" name="mode" type="int" enum="TextEdit.SelectionMode" /> <param index="0" name="mode" type="int" enum="TextEdit.SelectionMode" />
<param index="1" name="line" type="int" default="-1" /> <param index="1" name="line" type="int" default="-1" />
<param index="2" name="column" type="int" default="-1" /> <param index="2" name="column" type="int" default="-1" />
<param index="3" name="caret_index" type="int" default="0" />
<description> <description>
Sets the current selection mode. Sets the current selection mode.
</description> </description>
@ -890,6 +993,14 @@
Provide custom tooltip text. The callback method must take the following args: [code]hovered_word: String[/code] Provide custom tooltip text. The callback method must take the following args: [code]hovered_word: String[/code]
</description> </description>
</method> </method>
<method name="start_action">
<return type="void" />
<param index="0" name="action" type="int" enum="TextEdit.EditAction" />
<description>
Starts an action, will end the current action if [code]action[/code] is different.
An action will also end after a call to [method end_action], after [member ProjectSettings.gui/timers/text_edit_idle_detect_sec] is triggered or a new undoable step outside the [method start_action] and [method end_action] calls.
</description>
</method>
<method name="swap_lines"> <method name="swap_lines">
<return type="void" /> <return type="void" />
<param index="0" name="from_line" type="int" /> <param index="0" name="from_line" type="int" />
@ -926,6 +1037,9 @@
If [code]true[/code], a right-click moves the caret at the mouse position before displaying the context menu. If [code]true[/code], a right-click moves the caret at the mouse position before displaying the context menu.
If [code]false[/code], the context menu disregards mouse location. If [code]false[/code], the context menu disregards mouse location.
</member> </member>
<member name="caret_multiple" type="bool" setter="set_multiple_carets_enabled" getter="is_multiple_carets_enabled" default="true">
Sets if multiple carets are allowed.
</member>
<member name="caret_type" type="int" setter="set_caret_type" getter="get_caret_type" enum="TextEdit.CaretType" default="0"> <member name="caret_type" type="int" setter="set_caret_type" getter="get_caret_type" enum="TextEdit.CaretType" default="0">
Set the type of caret to draw. Set the type of caret to draw.
</member> </member>
@ -1154,6 +1268,18 @@
<constant name="MENU_MAX" value="28" enum="MenuItems"> <constant name="MENU_MAX" value="28" enum="MenuItems">
Represents the size of the [enum MenuItems] enum. Represents the size of the [enum MenuItems] enum.
</constant> </constant>
<constant name="ACTION_NONE" value="0" enum="EditAction">
No current action.
</constant>
<constant name="ACTION_TYPING" value="1" enum="EditAction">
A typing action.
</constant>
<constant name="ACTION_BACKSPACE" value="2" enum="EditAction">
A backwards delete action.
</constant>
<constant name="ACTION_DELETE" value="3" enum="EditAction">
A forward delete action.
</constant>
<constant name="SEARCH_MATCH_CASE" value="1" enum="SearchFlags"> <constant name="SEARCH_MATCH_CASE" value="1" enum="SearchFlags">
Match case when searching. Match case when searching.
</constant> </constant>

View file

@ -55,6 +55,7 @@ void GotoLineDialog::ok_pressed() {
if (get_line() < 1 || get_line() > text_editor->get_line_count()) { if (get_line() < 1 || get_line() > text_editor->get_line_count()) {
return; return;
} }
text_editor->remove_secondary_carets();
text_editor->unfold_line(get_line() - 1); text_editor->unfold_line(get_line() - 1);
text_editor->set_caret_line(get_line() - 1); text_editor->set_caret_line(get_line() - 1);
hide(); 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->unfold_line(pos.y);
text_editor->set_caret_line(pos.y, false); text_editor->set_caret_line(pos.y, false);
text_editor->set_caret_column(pos.x + text.length(), 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()); text_editor->select(pos.y, pos.x, pos.y, pos.x + text.length());
line_col_changed_for_result = true; 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() { void FindReplaceBar::_replace() {
bool selection_enabled = text_editor->has_selection(); bool selection_enabled = text_editor->has_selection(0);
Point2i selection_begin, selection_end; Point2i selection_begin, selection_end;
if (selection_enabled) { if (selection_enabled) {
selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_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(), text_editor->get_selection_to_column()); selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0));
} }
String replace_text = get_replace_text(); String replace_text = get_replace_text();
@ -188,25 +189,25 @@ void FindReplaceBar::_replace() {
text_editor->begin_complex_operation(); text_editor->begin_complex_operation();
if (selection_enabled && is_selection_only()) { // To restrict search_current() to selected region 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_line(selection_begin.width, false, true, 0, 0);
text_editor->set_caret_column(selection_begin.height); text_editor->set_caret_column(selection_begin.height, true, 0);
} }
if (search_current()) { if (search_current()) {
text_editor->unfold_line(result_line); 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()) { if (selection_enabled && is_selection_only()) {
Point2i match_from(result_line, result_col); Point2i match_from(result_line, result_col);
Point2i match_to(result_line, result_col + search_text_len); Point2i match_to(result_line, result_col + search_text_len);
if (!(match_from < selection_begin || match_to > selection_end)) { 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 if (match_to.x == selection_end.x) { // Adjust selection bounds if necessary
selection_end.y += replace_text.length() - search_text_len; selection_end.y += replace_text.length() - search_text_len;
} }
} }
} else { } else {
text_editor->insert_text_at_caret(replace_text); text_editor->insert_text_at_caret(replace_text, 0);
} }
} }
text_editor->end_complex_operation(); text_editor->end_complex_operation();
@ -216,29 +217,29 @@ void FindReplaceBar::_replace() {
if (selection_enabled && is_selection_only()) { if (selection_enabled && is_selection_only()) {
// Reselect in order to keep 'Replace' restricted to selection // 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 { } else {
text_editor->deselect(); text_editor->deselect(0);
} }
} }
void FindReplaceBar::_replace_all() { void FindReplaceBar::_replace_all() {
text_editor->disconnect("text_changed", callable_mp(this, &FindReplaceBar::_editor_text_changed)); text_editor->disconnect("text_changed", callable_mp(this, &FindReplaceBar::_editor_text_changed));
// Line as x so it gets priority in comparison, column as y. // 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); 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; Point2i selection_begin, selection_end;
if (selection_enabled) { if (selection_enabled) {
selection_begin = Point2i(text_editor->get_selection_from_line(), text_editor->get_selection_from_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(), text_editor->get_selection_to_column()); selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0));
} }
int vsval = text_editor->get_v_scroll(); int vsval = text_editor->get_v_scroll();
text_editor->set_caret_line(0); text_editor->set_caret_line(0, false, true, 0, 0);
text_editor->set_caret_column(0); text_editor->set_caret_column(0, true, 0);
String replace_text = get_replace_text(); String replace_text = get_replace_text();
int search_text_len = get_search_text().length(); int search_text_len = get_search_text().length();
@ -250,8 +251,8 @@ void FindReplaceBar::_replace_all() {
text_editor->begin_complex_operation(); text_editor->begin_complex_operation();
if (selection_enabled && is_selection_only()) { if (selection_enabled && is_selection_only()) {
text_editor->set_caret_line(selection_begin.width); text_editor->set_caret_line(selection_begin.width, false, true, 0, 0);
text_editor->set_caret_column(selection_begin.height); text_editor->set_caret_column(selection_begin.height, true, 0);
} }
if (search_current()) { if (search_current()) {
do { do {
@ -266,7 +267,7 @@ void FindReplaceBar::_replace_all() {
prev_match = Point2i(result_line, result_col + replace_text.length()); prev_match = Point2i(result_line, result_col + replace_text.length());
text_editor->unfold_line(result_line); 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 (selection_enabled && is_selection_only()) {
if (match_from < selection_begin || match_to > selection_end) { if (match_from < selection_begin || match_to > selection_end) {
@ -274,14 +275,14 @@ void FindReplaceBar::_replace_all() {
} }
// Replace but adjust selection bounds. // 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) { if (match_to.x == selection_end.x) {
selection_end.y += replace_text.length() - search_text_len; selection_end.y += replace_text.length() - search_text_len;
} }
} else { } else {
// Just replace. // Just replace.
text_editor->insert_text_at_caret(replace_text); text_editor->insert_text_at_caret(replace_text, 0);
} }
rc++; rc++;
@ -293,14 +294,14 @@ void FindReplaceBar::_replace_all() {
replace_all_mode = false; replace_all_mode = false;
// Restore editor state (selection, cursor, scroll). // Restore editor state (selection, cursor, scroll).
text_editor->set_caret_line(orig_cursor.x); text_editor->set_caret_line(orig_cursor.x, false, true, 0, 0);
text_editor->set_caret_column(orig_cursor.y); text_editor->set_caret_column(orig_cursor.y, true, 0);
if (selection_enabled && is_selection_only()) { if (selection_enabled && is_selection_only()) {
// Reselect. // 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 { } else {
text_editor->deselect(); text_editor->deselect(0);
} }
text_editor->set_v_scroll(vsval); 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) { void FindReplaceBar::_get_search_from(int &r_line, int &r_col) {
r_line = text_editor->get_caret_line(); r_line = text_editor->get_caret_line(0);
r_col = text_editor->get_caret_column(); 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; return;
} }
@ -434,7 +435,7 @@ bool FindReplaceBar::search_prev() {
int line, col; int line, col;
_get_search_from(line, col); _get_search_from(line, col);
if (text_editor->has_selection()) { if (text_editor->has_selection(0)) {
col--; // Skip currently selected word. 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")); search_text->call_deferred(SNAME("grab_focus"));
} }
if (text_editor->has_selection() && !selection_only->is_pressed()) { if (text_editor->has_selection(0) && !selection_only->is_pressed()) {
search_text->set_text(text_editor->get_selected_text()); search_text->set_text(text_editor->get_selected_text(0));
} }
if (!get_search_text().is_empty()) { if (!get_search_text().is_empty()) {
@ -548,9 +549,9 @@ void FindReplaceBar::popup_replace() {
hbc_option_replace->show(); 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) { 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) { 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(); _replace_all();
_hide_bar(); _hide_bar();
} else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { } else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
@ -1091,6 +1092,7 @@ void CodeTextEditor::trim_trailing_whitespace() {
} }
if (trimed_whitespace) { if (trimed_whitespace) {
text_editor->merge_overlapping_carets();
text_editor->end_complex_operation(); text_editor->end_complex_operation();
text_editor->queue_redraw(); text_editor->queue_redraw();
} }
@ -1122,8 +1124,11 @@ void CodeTextEditor::convert_indent_to_spaces() {
indent += " "; indent += " ";
} }
int cursor_line = text_editor->get_caret_line(); Vector<int> cursor_columns;
int cursor_column = text_editor->get_caret_column(); 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; bool changed_indentation = false;
for (int i = 0; i < text_editor->get_line_count(); i++) { 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(); text_editor->begin_complex_operation();
changed_indentation = true; changed_indentation = true;
} }
if (cursor_line == i && cursor_column > j) { for (int c = 0; c < text_editor->get_caret_count(); c++) {
cursor_column += indent_size - 1; 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); line = line.left(j) + indent + line.substr(j + 1);
} }
@ -1152,7 +1159,10 @@ void CodeTextEditor::convert_indent_to_spaces() {
} }
} }
if (changed_indentation) { 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->end_complex_operation();
text_editor->queue_redraw(); 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"); int indent_size = EditorSettings::get_singleton()->get("text_editor/behavior/indent/size");
indent_size -= 1; indent_size -= 1;
int cursor_line = text_editor->get_caret_line(); Vector<int> cursor_columns;
int cursor_column = text_editor->get_caret_column(); 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; bool changed_indentation = false;
for (int i = 0; i < text_editor->get_line_count(); i++) { 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(); text_editor->begin_complex_operation();
changed_indentation = true; changed_indentation = true;
} }
if (cursor_line == i && cursor_column > j) { for (int c = 0; c < text_editor->get_caret_count(); c++) {
cursor_column -= indent_size; 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); line = line.left(j - indent_size) + "\t" + line.substr(j + 1);
j = 0; j = 0;
@ -1201,7 +1216,10 @@ void CodeTextEditor::convert_indent_to_tabs() {
} }
} }
if (changed_indentation) { 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->end_complex_operation();
text_editor->queue_redraw(); text_editor->queue_redraw();
} }
@ -1211,59 +1229,128 @@ void CodeTextEditor::convert_case(CaseStyle p_case) {
if (!text_editor->has_selection()) { if (!text_editor->has_selection()) {
return; return;
} }
text_editor->begin_complex_operation(); text_editor->begin_complex_operation();
int begin = text_editor->get_selection_from_line(); Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
int end = text_editor->get_selection_to_line(); for (const int &c : caret_edit_order) {
int begin_col = text_editor->get_selection_from_column(); if (!text_editor->has_selection(c)) {
int end_col = text_editor->get_selection_to_column(); continue;
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) { int begin = text_editor->get_selection_from_line(c);
new_line = text_editor->get_line(i).left(begin_col) + new_line; 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(); text_editor->end_complex_operation();
} }
void CodeTextEditor::move_lines_up() { void CodeTextEditor::move_lines_up() {
text_editor->begin_complex_operation(); 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++) { Vector<int> carets_to_remove;
int line_id = i;
int next_id = i - 1; Vector<int> 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) { if (line_id == 0 || next_id < 0) {
return; return;
@ -1273,238 +1360,336 @@ void CodeTextEditor::move_lines_up() {
text_editor->unfold_line(next_id); text_editor->unfold_line(next_id);
text_editor->swap_lines(line_id, 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->end_complex_operation();
text_editor->merge_overlapping_carets();
text_editor->queue_redraw(); text_editor->queue_redraw();
} }
void CodeTextEditor::move_lines_down() { void CodeTextEditor::move_lines_down() {
text_editor->begin_complex_operation(); 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--) { Vector<int> carets_to_remove;
int line_id = i;
int next_id = i + 1; Vector<int> 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()) { 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(line_id);
text_editor->unfold_line(next_id); text_editor->unfold_line(next_id);
text_editor->swap_lines(line_id, 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->end_complex_operation();
text_editor->queue_redraw(); 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() // this is currently intended to be called within delete_lines()
// so `begin_complex_operation` is omitted here // so `begin_complex_operation` is omitted here
text_editor->set_line(p_line, ""); text_editor->set_line(p_line, "");
if (p_line == 0 && text_editor->get_line_count() > 1) { if (p_line == 0 && text_editor->get_line_count() > 1) {
text_editor->set_caret_line(1); text_editor->set_caret_line(1, p_caret == 0, true, 0, p_caret);
text_editor->set_caret_column(0); 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()) { if (p_line < text_editor->get_line_count()) {
text_editor->unfold_line(p_line); 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() { void CodeTextEditor::delete_lines() {
text_editor->begin_complex_operation(); 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); Vector<int> carets_to_remove;
text_editor->deselect();
for (int i = 0; i < count; i++) { Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
_delete_line(from_line); 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(); text_editor->end_complex_operation();
} }
void CodeTextEditor::duplicate_selection() { 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(); text_editor->begin_complex_operation();
for (int i = from_line; i <= to_line; i++) { Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
text_editor->unfold_line(i); for (const int &c : caret_edit_order) {
} const int cursor_column = text_editor->get_caret_column(c);
text_editor->deselect(); int from_line = text_editor->get_caret_line(c);
text_editor->insert_text_at_caret(new_text); int to_line = text_editor->get_caret_line(c);
text_editor->set_caret_line(cursor_new_line); int from_column = 0;
text_editor->set_caret_column(cursor_new_column); int to_column = 0;
if (selection_active) { int cursor_new_line = to_line + 1;
text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column); 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->end_complex_operation();
text_editor->queue_redraw(); text_editor->queue_redraw();
} }
void CodeTextEditor::toggle_inline_comment(const String &delimiter) { void CodeTextEditor::toggle_inline_comment(const String &delimiter) {
text_editor->begin_complex_operation(); 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. Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
if (text_editor->get_selection_to_column() == 0) { for (const int &c : caret_edit_order) {
end -= 1; 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(); // End of selection ends on the first column of the last line, ignore it.
int cursor_pos = text_editor->get_caret_column(); if (text_editor->get_selection_to_column(c) == 0) {
end -= 1;
// 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;
} }
}
for (int i = begin; i <= end; i++) {
String line_text = text_editor->get_line(i);
if (line_text.strip_edges().is_empty()) { int col_to = text_editor->get_selection_to_column(c);
line_text = delimiter; int cursor_pos = text_editor->get_caret_column(c);
} else {
if (is_commented) { // Check if all lines in the selected block are commented.
line_text = line_text.substr(delimiter.length(), line_text.length()); bool is_commented = true;
} else { for (int i = begin; i <= end; i++) {
line_text = delimiter + line_text; 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. if (line_text.strip_edges().is_empty()) {
int offset = (is_commented ? -1 : 1) * delimiter.length(); line_text = delimiter;
int col_from = text_editor->get_selection_from_column() > 0 ? text_editor->get_selection_from_column() + offset : 0; } 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) { // Adjust selection & cursor position.
cursor_pos += 1; 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) { if (is_commented && text_editor->get_caret_column(c) == text_editor->get_line(text_editor->get_caret_line(c)).length() + 1) {
col_to += offset; cursor_pos += 1;
} }
if (text_editor->get_caret_column() != 0) { if (text_editor->get_selection_to_column(c) != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line(c)).length() + 1) {
cursor_pos += offset; col_to += offset;
} }
text_editor->select(begin, col_from, text_editor->get_selection_to_line(), col_to); if (text_editor->get_caret_column(c) != 0) {
text_editor->set_caret_column(cursor_pos); cursor_pos += offset;
}
} else { text_editor->select(begin, col_from, text_editor->get_selection_to_line(c), col_to, c);
int begin = text_editor->get_caret_line(); text_editor->set_caret_column(cursor_pos, c == 0, c);
String line_text = text_editor->get_line(begin);
int delimiter_length = delimiter.length();
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 { } else {
line_text = delimiter + line_text; int begin = text_editor->get_caret_line(c);
col += delimiter_length; String line_text = text_editor->get_line(begin);
} int delimiter_length = delimiter.length();
text_editor->set_line(begin, line_text); int col = text_editor->get_caret_column(c);
text_editor->set_caret_column(col); 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->end_complex_operation();
text_editor->queue_redraw(); text_editor->queue_redraw();
} }
void CodeTextEditor::goto_line(int p_line) { void CodeTextEditor::goto_line(int p_line) {
text_editor->remove_secondary_carets();
text_editor->deselect(); text_editor->deselect();
text_editor->unfold_line(p_line); text_editor->unfold_line(p_line);
text_editor->call_deferred(SNAME("set_caret_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) { 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->unfold_line(p_line);
text_editor->call_deferred(SNAME("set_caret_line"), p_line); text_editor->call_deferred(SNAME("set_caret_line"), p_line);
text_editor->call_deferred(SNAME("set_caret_column"), p_begin); text_editor->call_deferred(SNAME("set_caret_column"), p_begin);
@ -1617,6 +1802,7 @@ void CodeTextEditor::goto_error() {
if (text_editor->get_line_count() != error_line) { if (text_editor->get_line_count() != error_line) {
text_editor->unfold_line(error_line); text_editor->unfold_line(error_line);
} }
text_editor->remove_secondary_carets();
text_editor->set_caret_line(error_line); text_editor->set_caret_line(error_line);
text_editor->set_caret_column(error_column); text_editor->set_caret_column(error_column);
text_editor->center_viewport_to_caret(); text_editor->center_viewport_to_caret();
@ -1793,8 +1979,10 @@ void CodeTextEditor::set_warning_count(int p_warning_count) {
} }
void CodeTextEditor::toggle_bookmark() { void CodeTextEditor::toggle_bookmark() {
int line = text_editor->get_caret_line(); for (int i = 0; i < text_editor->get_caret_count(); i++) {
text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line)); 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() { void CodeTextEditor::goto_next_bookmark() {
@ -1803,6 +1991,7 @@ void CodeTextEditor::goto_next_bookmark() {
return; return;
} }
text_editor->remove_secondary_carets();
int line = text_editor->get_caret_line(); int line = text_editor->get_caret_line();
if (line >= (int)bmarks[bmarks.size() - 1]) { if (line >= (int)bmarks[bmarks.size() - 1]) {
text_editor->unfold_line(bmarks[0]); text_editor->unfold_line(bmarks[0]);
@ -1827,6 +2016,7 @@ void CodeTextEditor::goto_prev_bookmark() {
return; return;
} }
text_editor->remove_secondary_carets();
int line = text_editor->get_caret_line(); int line = text_editor->get_caret_line();
if (line <= (int)bmarks[0]) { if (line <= (int)bmarks[0]) {
text_editor->unfold_line(bmarks[bmarks.size() - 1]); text_editor->unfold_line(bmarks[bmarks.size() - 1]);

View file

@ -197,7 +197,7 @@ class CodeTextEditor : public VBoxContainer {
void _update_status_bar_theme(); void _update_status_bar_theme();
void _delete_line(int p_line); void _delete_line(int p_line, int p_caret);
void _toggle_scripts_pressed(); void _toggle_scripts_pressed();
protected: protected:

View file

@ -267,6 +267,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
void ScriptTextEditor::_error_clicked(Variant p_line) { void ScriptTextEditor::_error_clicked(Variant p_line) {
if (p_line.get_type() == Variant::INT) { 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()); 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) { void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
String code = code_editor->get_text_editor()->get_text(); String code = code_editor->get_text_editor()->get_text();
int pos = script->get_language()->find_function(p_function, code); int pos = script->get_language()->find_function(p_function, code);
code_editor->get_text_editor()->remove_secondary_carets();
if (pos == -1) { if (pos == -1) {
//does not exist //does not exist
code_editor->get_text_editor()->deselect(); code_editor->get_text_editor()->deselect();
@ -1367,6 +1369,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
return; return;
} }
tx->remove_secondary_carets();
int line = tx->get_caret_line(); int line = tx->get_caret_line();
// wrap around // wrap around
@ -1393,6 +1396,7 @@ void ScriptTextEditor::_edit_option(int p_op) {
return; return;
} }
tx->remove_secondary_carets();
int line = tx->get_caret_line(); int line = tx->get_caret_line();
// wrap around // wrap around
if (line <= (int)bpoints[0]) { if (line <= (int)bpoints[0]) {
@ -1413,21 +1417,21 @@ void ScriptTextEditor::_edit_option(int p_op) {
} break; } break;
case HELP_CONTEXTUAL: { case HELP_CONTEXTUAL: {
String text = tx->get_selected_text(); String text = tx->get_selected_text(0);
if (text.is_empty()) { if (text.is_empty()) {
text = tx->get_word_under_caret(); text = tx->get_word_under_caret(0);
} }
if (!text.is_empty()) { if (!text.is_empty()) {
emit_signal(SNAME("request_help"), text); emit_signal(SNAME("request_help"), text);
} }
} break; } break;
case LOOKUP_SYMBOL: { case LOOKUP_SYMBOL: {
String text = tx->get_word_under_caret(); String text = tx->get_word_under_caret(0);
if (text.is_empty()) { if (text.is_empty()) {
text = tx->get_selected_text(); text = tx->get_selected_text(0);
} }
if (!text.is_empty()) { 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; } break;
} }
@ -1605,6 +1609,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
int col = pos.x; int col = pos.x;
if (d.has("type") && String(d["type"]) == "resource") { if (d.has("type") && String(d["type"]) == "resource") {
te->remove_secondary_carets();
Ref<Resource> res = d["resource"]; Ref<Resource> res = d["resource"];
if (!res.is_valid()) { if (!res.is_valid()) {
return; return;
@ -1622,6 +1627,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")) { if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) {
te->remove_secondary_carets();
Array files = d["files"]; Array files = d["files"];
String text_to_drop; String text_to_drop;
@ -1645,6 +1651,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
} }
if (d.has("type") && String(d["type"]) == "nodes") { if (d.has("type") && String(d["type"]) == "nodes") {
te->remove_secondary_carets();
Node *scene_root = get_tree()->get_edited_scene_root(); Node *scene_root = get_tree()->get_edited_scene_root();
if (!scene_root) { if (!scene_root) {
EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene.")); EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene."));
@ -1729,6 +1736,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
} }
if (d.has("type") && String(d["type"]) == "obj_property") { 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); const String text_to_drop = String(d["property"]).c_escape().quote(quote_style);
te->set_caret_line(row); te->set_caret_line(row);
@ -1749,8 +1757,8 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
local_pos = mb->get_global_position() - tx->get_global_position(); local_pos = mb->get_global_position() - tx->get_global_position();
create_menu = true; create_menu = true;
} else if (k.is_valid() && k->is_action("ui_menu", true)) { } else if (k.is_valid() && k->is_action("ui_menu", true)) {
tx->adjust_viewport_to_caret(); tx->adjust_viewport_to_caret(0);
local_pos = tx->get_caret_draw_pos(); local_pos = tx->get_caret_draw_pos(0);
create_menu = true; create_menu = true;
} }
@ -1761,6 +1769,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); 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()) { if (tx->is_move_caret_on_right_click_enabled()) {
tx->remove_secondary_carets();
if (tx->has_selection()) { if (tx->has_selection()) {
int from_line = tx->get_selection_from_line(); int from_line = tx->get_selection_from_line();
int to_line = tx->get_selection_to_line(); int to_line = tx->get_selection_to_line();
@ -1780,10 +1789,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
String word_at_pos = tx->get_word_at_pos(local_pos); String word_at_pos = tx->get_word_at_pos(local_pos);
if (word_at_pos.is_empty()) { 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()) { 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"); bool has_color = (word_at_pos == "Color");

View file

@ -445,6 +445,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
bool is_folded = tx->is_line_folded(row); bool is_folded = tx->is_line_folded(row);
if (tx->is_move_caret_on_right_click_enabled()) { if (tx->is_move_caret_on_right_click_enabled()) {
tx->remove_secondary_carets();
if (tx->has_selection()) { if (tx->has_selection()) {
int from_line = tx->get_selection_from_line(); int from_line = tx->get_selection_from_line();
int to_line = tx->get_selection_to_line(); int to_line = tx->get_selection_to_line();
@ -471,9 +472,9 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
Ref<InputEventKey> k = ev; Ref<InputEventKey> k = ev;
if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) { if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
CodeEdit *tx = code_editor->get_text_editor(); CodeEdit *tx = code_editor->get_text_editor();
int line = tx->get_caret_line(); int line = tx->get_caret_line(0);
tx->adjust_viewport_to_caret(); tx->adjust_viewport_to_caret(0);
_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())); _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(); context_menu->grab_focus();
} }
} }

View file

@ -958,6 +958,7 @@ void TextShaderEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
tx->set_move_caret_on_right_click_enabled(EditorSettings::get_singleton()->get("text_editor/behavior/navigation/move_caret_on_right_click")); 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()) { if (tx->is_move_caret_on_right_click_enabled()) {
tx->remove_secondary_carets();
if (tx->has_selection()) { if (tx->has_selection()) {
int from_line = tx->get_selection_from_line(); int from_line = tx->get_selection_from_line();
int to_line = tx->get_selection_to_line(); int to_line = tx->get_selection_to_line();

View file

@ -609,72 +609,74 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
/* Text manipulation */ /* Text manipulation */
// Overridable actions // Overridable actions
void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) { void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
bool had_selection = has_selection(); start_action(EditAction::ACTION_TYPING);
String selection_text = (had_selection ? get_selected_text() : ""); Vector<int> caret_edit_order = get_caret_index_edit_order();
for (const int &i : caret_edit_order) {
if (had_selection) { if (p_caret != -1 && p_caret != i) {
begin_complex_operation(); continue;
delete_selection();
}
// Remove the old character if in overtype mode and no selection.
if (is_overtype_mode_enabled() && !had_selection) {
begin_complex_operation();
/* Make sure we don't try and remove empty space. */
if (get_caret_column() < get_line(get_caret_line()).length()) {
remove_text(get_caret_line(), get_caret_column(), get_caret_line(), get_caret_column() + 1);
} }
}
const char32_t chr[2] = { (char32_t)p_unicode, 0 }; bool had_selection = has_selection(i);
String selection_text = (had_selection ? get_selected_text(i) : "");
if (auto_brace_completion_enabled) {
int cl = get_caret_line();
int cc = get_caret_column();
if (had_selection) { if (had_selection) {
insert_text_at_caret(chr); delete_selection(i);
}
String close_key = get_auto_brace_completion_close_key(chr); // Remove the old character if in overtype mode and no selection.
if (!close_key.is_empty()) { if (is_overtype_mode_enabled() && !had_selection) {
insert_text_at_caret(selection_text + close_key); // Make sure we don't try and remove empty space.
set_caret_column(get_caret_column() - 1); if (get_caret_column(i) < get_line(get_caret_line(i)).length()) {
remove_text(get_caret_line(i), get_caret_column(i), get_caret_line(i), get_caret_column(i) + 1);
}
}
const char32_t chr[2] = { (char32_t)p_unicode, 0 };
if (auto_brace_completion_enabled) {
int cl = get_caret_line(i);
int cc = get_caret_column(i);
if (had_selection) {
insert_text_at_caret(chr, i);
String close_key = get_auto_brace_completion_close_key(chr);
if (!close_key.is_empty()) {
insert_text_at_caret(selection_text + close_key, i);
set_caret_column(get_caret_column(i) - 1, i == 0, i);
}
} else {
int caret_move_offset = 1;
int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
insert_text_at_caret(chr, i);
} else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
insert_text_at_caret(chr, i);
} else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
} else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) {
insert_text_at_caret(chr, i);
} else {
insert_text_at_caret(chr, i);
int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1);
if (pre_brace_pair != -1) {
insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
}
}
set_caret_column(cc + caret_move_offset, i == 0, i);
} }
} else { } else {
int caret_move_offset = 1; insert_text_at_caret(chr, i);
int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
insert_text_at_caret(chr);
} else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
insert_text_at_caret(chr);
} else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
} else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) {
insert_text_at_caret(chr);
} else {
insert_text_at_caret(chr);
int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1);
if (pre_brace_pair != -1) {
insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key);
}
}
set_caret_column(cc + caret_move_offset);
} }
} else {
insert_text_at_caret(chr);
}
if ((is_overtype_mode_enabled() && !had_selection) || (had_selection)) {
end_complex_operation();
} }
end_action();
} }
void CodeEdit::_backspace_internal() { void CodeEdit::_backspace_internal(int p_caret) {
if (!is_editable()) { if (!is_editable()) {
return; return;
} }
@ -684,51 +686,65 @@ void CodeEdit::_backspace_internal() {
return; return;
} }
int cc = get_caret_column(); begin_complex_operation();
int cl = get_caret_line(); Vector<int> caret_edit_order = get_caret_index_edit_order();
for (const int &i : caret_edit_order) {
if (p_caret != -1 && p_caret != i) {
continue;
}
if (cc == 0 && cl == 0) { int cc = get_caret_column(i);
return; int cl = get_caret_line(i);
}
if (cl > 0 && _is_line_hidden(cl - 1)) { if (cc == 0 && cl == 0) {
unfold_line(get_caret_line() - 1); continue;
} }
int prev_line = cc ? cl : cl - 1; if (cl > 0 && _is_line_hidden(cl - 1)) {
int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length()); unfold_line(get_caret_line(i) - 1);
}
merge_gutters(prev_line, cl); int prev_line = cc ? cl : cl - 1;
int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
if (auto_brace_completion_enabled && cc > 0) { merge_gutters(prev_line, cl);
int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
if (idx != -1) {
prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) { if (auto_brace_completion_enabled && cc > 0) {
remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length()); int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
} else { if (idx != -1) {
remove_text(prev_line, prev_column, cl, cc); prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
} else {
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 + auto_brace_completion_pairs[idx].close_key.length());
continue;
} }
set_caret_line(prev_line, false, true);
set_caret_column(prev_column);
return;
} }
}
// For space indentation we need to do a simple unindent if there are no chars to the left, acting in the // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the
// same way as tabs. // same way as tabs.
if (indent_using_spaces && cc != 0) { if (indent_using_spaces && cc != 0) {
if (get_first_non_whitespace_column(cl) >= cc) { if (get_first_non_whitespace_column(cl) >= cc) {
prev_column = cc - _calculate_spaces_till_next_left_indent(cc); prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
prev_line = cl; prev_line = cl;
}
} }
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);
} }
merge_overlapping_carets();
remove_text(prev_line, prev_column, cl, cc); end_complex_operation();
set_caret_line(prev_line, false, true);
set_caret_column(prev_column);
} }
/* Indent management */ /* Indent management */
@ -803,10 +819,15 @@ void CodeEdit::do_indent() {
return; return;
} }
int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column()); begin_complex_operation();
if (spaces_to_add > 0) { Vector<int> caret_edit_order = get_caret_index_edit_order();
insert_text_at_caret(String(" ").repeat(spaces_to_add)); for (const int &i : caret_edit_order) {
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_complex_operation();
} }
void CodeEdit::indent_lines() { void CodeEdit::indent_lines() {
@ -815,48 +836,49 @@ void CodeEdit::indent_lines() {
} }
begin_complex_operation(); 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;
/* This value informs us by how much we changed selection position by indenting right. */ int start_line = get_caret_line(c);
/* Default is 1 for tab indentation. */ int end_line = start_line;
int selection_offset = 1; if (has_selection(c)) {
start_line = get_selection_from_line(c);
end_line = get_selection_to_line(c);
int start_line = get_caret_line(); // Ignore the last line if the selection is not past the first column.
int end_line = start_line; if (get_selection_to_column(c) == 0) {
if (has_selection()) { selection_offset = 0;
start_line = get_selection_from_line(); end_line--;
end_line = get_selection_to_line(); }
/* Ignore the last line if the selection is not past the first column. */
if (get_selection_to_column() == 0) {
selection_offset = 0;
end_line--;
}
}
for (int i = start_line; i <= end_line; i++) {
const String line_text = get_line(i);
if (line_text.size() == 0 && has_selection()) {
continue;
} }
if (!indent_using_spaces) { for (int i = start_line; i <= end_line; i++) {
set_line(i, '\t' + line_text); const String line_text = get_line(i);
continue; if (line_text.size() == 0 && has_selection(c)) {
continue;
}
if (!indent_using_spaces) {
set_line(i, '\t' + line_text);
continue;
}
// 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;
} }
/* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */ // Fix selection and caret being off after shifting selection right.
/* Since we will add this many spaces, we want to move the whole selection and caret by this much. */ if (has_selection(c)) {
int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i)); select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c);
set_line(i, String(" ").repeat(spaces_to_add) + line_text); }
selection_offset = spaces_to_add; set_caret_column(get_caret_column(c) + selection_offset, false, c);
} }
/* Fix selection and caret being off after shifting selection right.*/
if (has_selection()) {
select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset);
}
set_caret_column(get_caret_column() + selection_offset, false);
end_complex_operation(); end_complex_operation();
} }
@ -872,30 +894,36 @@ void CodeEdit::do_unindent() {
return; return;
} }
int cl = get_caret_line(); begin_complex_operation();
const String &line = get_line(cl); Vector<int> caret_edit_order = get_caret_index_edit_order();
for (const int &c : caret_edit_order) {
int cl = get_caret_line(c);
const String &line = get_line(cl);
if (line[cc - 1] == '\t') { if (line[cc - 1] == '\t') {
remove_text(cl, cc - 1, cl, cc); remove_text(cl, cc - 1, cl, cc);
set_caret_column(MAX(0, cc - 1)); set_caret_column(MAX(0, cc - 1), c == 0, c);
return; adjust_carets_after_edit(c, cl, cc, cl, cc - 1);
} continue;
}
if (line[cc - 1] != ' ') {
return; if (line[cc - 1] != ' ') {
} continue;
}
int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
if (spaces_to_remove > 0) { int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
for (int i = 1; i <= spaces_to_remove; i++) { if (spaces_to_remove > 0) {
if (line[cc - i] != ' ') { for (int i = 1; i <= spaces_to_remove; i++) {
spaces_to_remove = i - 1; if (line[cc - i] != ' ') {
break; spaces_to_remove = i - 1;
} break;
}
}
remove_text(cl, cc - spaces_to_remove, cl, cc);
set_caret_column(MAX(0, cc - spaces_to_remove), c == 0, c);
} }
remove_text(cl, cc - spaces_to_remove, cl, cc);
set_caret_column(MAX(0, cc - spaces_to_remove));
} }
end_complex_operation();
} }
void CodeEdit::unindent_lines() { void CodeEdit::unindent_lines() {
@ -905,71 +933,73 @@ void CodeEdit::unindent_lines() {
begin_complex_operation(); begin_complex_operation();
/* Moving caret and selection after unindenting can get tricky because */ Vector<int> caret_edit_order = get_caret_index_edit_order();
/* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */ for (const int &c : caret_edit_order) {
/* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */ // Moving caret and selection after unindenting can get tricky because
int removed_characters = 0; // changing content of line can move caret and selection on its own (if new line ends before previous position of either)
int initial_selection_end_column = 0; // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
int initial_cursor_column = get_caret_column(); int removed_characters = 0;
int initial_selection_end_column = 0;
int initial_cursor_column = get_caret_column(c);
int start_line = get_caret_line(); int start_line = get_caret_line(c);
int end_line = start_line; int end_line = start_line;
if (has_selection()) { if (has_selection(c)) {
start_line = get_selection_from_line(); start_line = get_selection_from_line(c);
end_line = get_selection_to_line(); end_line = get_selection_to_line(c);
/* Ignore the last line if the selection is not past the first column. */ // Ignore the last line if the selection is not past the first column.
initial_selection_end_column = get_selection_to_column(); initial_selection_end_column = get_selection_to_column(c);
if (initial_selection_end_column == 0) { if (initial_selection_end_column == 0) {
end_line--; 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);
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.
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);
}
}
set_caret_column(initial_cursor_column - removed_characters, false, c);
} }
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);
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. */
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()) {
/* Fix selection being off by one on the first line. */
if (first_line_edited) {
select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column);
}
/* Fix selection being off by one on the last line. */
if (last_line_edited) {
select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
}
}
set_caret_column(initial_cursor_column - removed_characters, false);
end_complex_operation(); end_complex_operation();
} }
@ -990,106 +1020,108 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
return; return;
} }
/* When not splitting the line, we need to factor in indentation from the end of the current line. */ begin_complex_operation();
const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length(); Vector<int> caret_edit_order = get_caret_index_edit_order();
const int cl = get_caret_line(); for (const int &i : caret_edit_order) {
// 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); const String line = get_line(cl);
String ins = "\n"; String ins = "\n";
/* Append current indentation. */ // Append current indentation.
int space_count = 0; int space_count = 0;
int line_col = 0; int line_col = 0;
for (; line_col < cc; line_col++) { for (; line_col < cc; line_col++) {
if (line[line_col] == '\t') { if (line[line_col] == '\t') {
ins += indent_text;
space_count = 0;
continue;
}
if (line[line_col] == ' ') {
space_count++;
if (space_count == indent_size) {
ins += indent_text; ins += indent_text;
space_count = 0; space_count = 0;
}
continue;
}
break;
}
if (is_line_folded(cl)) {
unfold_line(cl);
}
/* Indent once again if the previous line needs it, ie ':'. */
/* Then add an addition new line for any closing pairs aka '()'. */
/* Skip this in comments or if we are going above. */
bool brace_indent = false;
if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
bool should_indent = false;
char32_t indent_char = ' ';
for (; line_col < cc; line_col++) {
char32_t c = line[line_col];
if (auto_indent_prefixes.has(c)) {
should_indent = true;
indent_char = c;
continue; continue;
} }
/* Make sure this is the last char, trailing whitespace or comments are okay. */ if (line[line_col] == ' ') {
/* Increment column for comments because the delimiter (#) should be ignored. */ space_count++;
if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
should_indent = false; if (space_count == indent_size) {
ins += indent_text;
space_count = 0;
}
continue;
} }
break;
} }
if (should_indent) { if (is_line_folded(cl)) {
ins += indent_text; unfold_line(cl);
}
String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char)); // Indent once again if the previous line needs it, ie ':'.
if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) { // Then add an addition new line for any closing pairs aka '()'.
/* No need to move the brace below if we are not taking the text with us. */ // Skip this in comments or if we are going above.
if (p_split_current_line) { bool brace_indent = false;
brace_indent = true; if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); bool should_indent = false;
} else { char32_t indent_char = ' ';
brace_indent = false;
ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); for (; line_col < cc; line_col++) {
char32_t c = line[line_col];
if (auto_indent_prefixes.has(c)) {
should_indent = true;
indent_char = c;
continue;
}
// Make sure this is the last char, trailing whitespace or comments are okay.
// Increment column for comments because the delimiter (#) should be ignored.
if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
should_indent = false;
}
}
if (should_indent) {
ins += indent_text;
String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char));
if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) {
// No need to move the brace below if we are not taking the text with us.
if (p_split_current_line) {
brace_indent = true;
ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2);
} else {
brace_indent = false;
ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2);
}
} }
} }
} }
}
begin_complex_operation(); bool first_line = false;
if (!p_split_current_line) {
deselect(i);
bool first_line = false; if (p_above) {
if (!p_split_current_line) { if (cl > 0) {
deselect(); set_caret_line(cl - 1, false, true, 0, i);
set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
if (p_above) { } else {
if (cl > 0) { set_caret_column(0, i == 0, i);
set_caret_line(cl - 1, false); first_line = true;
set_caret_column(get_line(get_caret_line()).length()); }
} else { } else {
set_caret_column(0); set_caret_column(line.length(), i == 0, i);
first_line = true;
} }
} else {
set_caret_column(line.length());
} }
}
insert_text_at_caret(ins); insert_text_at_caret(ins, i);
if (first_line) { if (first_line) {
set_caret_line(0); set_caret_line(0, i == 0, true, 0, i);
} else if (brace_indent) { } else if (brace_indent) {
set_caret_line(get_caret_line() - 1, false); set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
set_caret_column(get_line(get_caret_line()).length()); set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
}
} }
end_complex_operation(); end_complex_operation();
@ -1522,22 +1554,26 @@ void CodeEdit::fold_line(int p_line) {
_set_line_as_hidden(i, true); _set_line_as_hidden(i, true);
} }
/* Fix selection. */ for (int i = 0; i < get_caret_count(); i++) {
if (has_selection()) { // Fix selection.
if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) { if (has_selection(i)) {
deselect(); if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) {
} else if (_is_line_hidden(get_selection_from_line())) { deselect(i);
select(p_line, 9999, get_selection_to_line(), get_selection_to_column()); } else if (_is_line_hidden(get_selection_from_line(i))) {
} else if (_is_line_hidden(get_selection_to_line())) { select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i);
select(get_selection_from_line(), get_selection_from_column(), p_line, 9999); } 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);
} }
} }
/* Reset caret. */ merge_overlapping_carets();
if (_is_line_hidden(get_caret_line())) {
set_caret_line(p_line, false, false);
set_caret_column(get_line(p_line).length(), false);
}
queue_redraw(); queue_redraw();
} }
@ -1950,98 +1986,108 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
return; return;
} }
char32_t caret_last_completion_char;
begin_complex_operation(); begin_complex_operation();
Vector<int> caret_edit_order = get_caret_index_edit_order();
for (const int &i : caret_edit_order) {
int caret_line = get_caret_line(i);
int caret_line = get_caret_line(); const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
const String &display_text = code_completion_options[code_completion_current_selected].display;
const String &insert_text = code_completion_options[code_completion_current_selected].insert_text; if (p_replace) {
const String &display_text = code_completion_options[code_completion_current_selected].display; // Find end of current section.
const String line = get_line(caret_line);
int caret_col = get_caret_column(i);
int caret_remove_line = caret_line;
if (p_replace) { bool merge_text = true;
/* Find end of current section */ int in_string = is_in_string(caret_line, caret_col);
const String line = get_line(caret_line); if (in_string != -1) {
int caret_col = get_caret_column(); Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
int caret_remove_line = caret_line; if (string_end.x != -1) {
merge_text = false;
bool merge_text = true; caret_remove_line = string_end.y;
int in_string = is_in_string(caret_line, caret_col); caret_col = string_end.x - 1;
if (in_string != -1) { }
Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
if (string_end.x != -1) {
merge_text = false;
caret_remove_line = string_end.y;
caret_col = string_end.x - 1;
} }
if (merge_text) {
for (; caret_col < line.length(); caret_col++) {
if (is_symbol(line[caret_col])) {
break;
}
}
}
// 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.
const String line = get_line(caret_line);
int caret_col = get_caret_column(i);
int matching_chars = code_completion_base.length();
for (; matching_chars <= insert_text.length(); matching_chars++) {
if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
break;
}
caret_col++;
}
// 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);
set_caret_column(caret_col, false, i);
insert_text_at_caret(insert_text.substr(matching_chars), i);
} }
if (merge_text) { //* Handle merging of symbols eg strings, brackets.
for (; caret_col < line.length(); caret_col++) { const String line = get_line(caret_line);
if (is_symbol(line[caret_col])) { char32_t next_char = line[get_caret_column(i)];
break; char32_t last_completion_char = insert_text[insert_text.length() - 1];
if (i == 0) {
caret_last_completion_char = last_completion_char;
}
char32_t last_completion_char_display = display_text[display_text.length() - 1];
int pre_brace_pair = get_caret_column(i) > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i)) : -1;
int post_brace_pair = get_caret_column(i) < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i)) : -1;
if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) {
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);
}
if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) {
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 && post_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);
}
if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column(i) > 0 && get_caret_column(i) < get_line(caret_line).length()) {
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);
} }
} }
} }
/* Replace. */
remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_remove_line, caret_col);
set_caret_column(get_caret_column() - code_completion_base.length(), false);
insert_text_at_caret(insert_text);
} else {
/* Get first non-matching char. */
const String line = get_line(caret_line);
int caret_col = get_caret_column();
int matching_chars = code_completion_base.length();
for (; matching_chars <= insert_text.length(); matching_chars++) {
if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
break;
}
caret_col++;
}
/* Remove base completion text. */
remove_text(caret_line, get_caret_column() - code_completion_base.length(), caret_line, get_caret_column());
set_caret_column(get_caret_column() - code_completion_base.length(), false);
/* Merge with text. */
insert_text_at_caret(insert_text.substr(0, code_completion_base.length()));
set_caret_column(caret_col, false);
insert_text_at_caret(insert_text.substr(matching_chars));
} }
/* Handle merging of symbols eg strings, brackets. */
const String line = get_line(caret_line);
char32_t next_char = line[get_caret_column()];
char32_t last_completion_char = insert_text[insert_text.length() - 1];
char32_t last_completion_char_display = display_text[display_text.length() - 1];
int pre_brace_pair = get_caret_column() > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column()) : -1;
int post_brace_pair = get_caret_column() < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column()) : -1;
if (post_brace_pair != -1 && (last_completion_char == next_char || last_completion_char_display == next_char)) {
remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1);
}
if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && (last_completion_char == next_char || last_completion_char_display == next_char)) {
remove_text(caret_line, get_caret_column(), caret_line, get_caret_column() + 1);
} else if (auto_brace_completion_enabled && pre_brace_pair != -1 && post_brace_pair == -1) {
insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key);
set_caret_column(get_caret_column() - auto_brace_completion_pairs[pre_brace_pair].close_key.length());
}
if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column() > 0 && get_caret_column() < get_line(caret_line).length()) {
pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column() + 1);
if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1)) {
remove_text(caret_line, get_caret_column() - 2, caret_line, get_caret_column());
if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column() - 1) != pre_brace_pair) {
set_caret_column(get_caret_column() - 1);
}
}
}
end_complex_operation(); end_complex_operation();
cancel_code_completion(); cancel_code_completion();
if (code_completion_prefixes.has(last_completion_char)) { if (code_completion_prefixes.has(caret_last_completion_char)) {
request_code_completion(); request_code_completion();
} }
} }
@ -2399,6 +2445,7 @@ void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
} }
if (p_gutter == line_number_gutter) { if (p_gutter == line_number_gutter) {
remove_secondary_carets();
set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0); set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0);
select(p_line, 0, p_line + 1, 0); select(p_line, 0, p_line + 1, 0);
set_caret_line(p_line + 1); set_caret_line(p_line + 1);

View file

@ -261,8 +261,8 @@ protected:
/* Text manipulation */ /* Text manipulation */
// Overridable actions // Overridable actions
virtual void _handle_unicode_input_internal(const uint32_t p_unicode) override; virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override;
virtual void _backspace_internal() override; virtual void _backspace_internal(int p_caret) override;
GDVIRTUAL1(_confirm_code_completion, bool) GDVIRTUAL1(_confirm_code_completion, bool)
GDVIRTUAL1(_request_code_completion, bool) GDVIRTUAL1(_request_code_completion, bool)

File diff suppressed because it is too large Load diff

View file

@ -42,6 +42,14 @@ class TextEdit : public Control {
GDCLASS(TextEdit, Control); GDCLASS(TextEdit, Control);
public: public:
/* Edit Actions. */
enum EditAction {
ACTION_NONE,
ACTION_TYPING,
ACTION_BACKSPACE,
ACTION_DELETE,
};
/* Caret. */ /* Caret. */
enum CaretType { enum CaretType {
CARET_TYPE_LINE, CARET_TYPE_LINE,
@ -299,12 +307,15 @@ private:
Key _get_menu_action_accelerator(const String &p_action); Key _get_menu_action_accelerator(const String &p_action);
/* Versioning */ /* Versioning */
struct Caret;
struct TextOperation { struct TextOperation {
enum Type { enum Type {
TYPE_NONE, TYPE_NONE,
TYPE_INSERT, TYPE_INSERT,
TYPE_REMOVE TYPE_REMOVE
}; };
Vector<Caret> start_carets;
Vector<Caret> end_carets;
Type type = TYPE_NONE; Type type = TYPE_NONE;
int from_line = 0; int from_line = 0;
@ -321,6 +332,10 @@ private:
bool undo_enabled = true; bool undo_enabled = true;
int undo_stack_max_size = 50; int undo_stack_max_size = 50;
EditAction current_action = EditAction::ACTION_NONE;
bool pending_action_end = false;
bool in_action = false;
int complex_operation_count = 0; int complex_operation_count = 0;
bool next_operation_is_complex = false; bool next_operation_is_complex = false;
@ -361,19 +376,39 @@ private:
int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const; int _get_char_pos_for_line(int p_px, int p_line, int p_wrap_index = 0) const;
/* Caret. */ /* Caret. */
struct Selection {
bool active = false;
bool shiftclick_left = false;
int selecting_line = 0;
int selecting_column = 0;
int selected_word_beg = 0;
int selected_word_end = 0;
int selected_word_origin = 0;
int from_line = 0;
int from_column = 0;
int to_line = 0;
int to_column = 0;
};
struct Caret { struct Caret {
Selection selection;
Point2 draw_pos; Point2 draw_pos;
bool visible = false; bool visible = false;
int last_fit_x = 0; int last_fit_x = 0;
int line = 0; int line = 0;
int column = 0; int column = 0;
int x_ofs = 0; };
int line_ofs = 0;
int wrap_ofs = 0; // Vector containing all the carets, index '0' is the "main caret" and should never be removed.
} caret; Vector<Caret> carets;
Vector<int> caret_index_edit_order;
bool setting_caret_line = false; bool setting_caret_line = false;
bool caret_pos_dirty = false; bool caret_pos_dirty = false;
bool caret_index_edit_dirty = true;
Color caret_color = Color(1, 1, 1); Color caret_color = Color(1, 1, 1);
Color caret_background_color = Color(0, 0, 0); Color caret_background_color = Color(0, 0, 0);
@ -389,6 +424,8 @@ private:
bool caret_mid_grapheme_enabled = true; bool caret_mid_grapheme_enabled = true;
bool multi_carets_enabled = true;
bool drag_action = false; bool drag_action = false;
bool drag_caret_force_displayed = false; bool drag_caret_force_displayed = false;
@ -397,28 +434,10 @@ private:
void _reset_caret_blink_timer(); void _reset_caret_blink_timer();
void _toggle_draw_caret(); void _toggle_draw_caret();
int _get_column_x_offset_for_line(int p_char, int p_line) const; int _get_column_x_offset_for_line(int p_char, int p_line, int p_column) const;
/* Selection. */ /* Selection. */
struct Selection { SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
int selecting_line = 0;
int selecting_column = 0;
int selected_word_beg = 0;
int selected_word_end = 0;
int selected_word_origin = 0;
bool selecting_text = false;
bool active = false;
int from_line = 0;
int from_column = 0;
int to_line = 0;
int to_column = 0;
bool shiftclick_left = false;
bool drag_attempt = false;
} selection;
bool selecting_enabled = true; bool selecting_enabled = true;
bool deselect_on_focus_loss_enabled = true; bool deselect_on_focus_loss_enabled = true;
@ -428,6 +447,7 @@ private:
Color selection_color = Color(1, 1, 1); Color selection_color = Color(1, 1, 1);
bool override_selected_font_color = false; bool override_selected_font_color = false;
bool selection_drag_attempt = false;
bool dragging_selection = false; bool dragging_selection = false;
Timer *click_select_held = nullptr; Timer *click_select_held = nullptr;
@ -439,8 +459,8 @@ private:
void _update_selection_mode_word(); void _update_selection_mode_word();
void _update_selection_mode_line(); void _update_selection_mode_line();
void _pre_shift_selection(); void _pre_shift_selection(int p_caret);
void _post_shift_selection(); void _post_shift_selection(int p_caret);
/* Line wrapping. */ /* Line wrapping. */
LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE; LineWrappingMode line_wrapping_mode = LineWrappingMode::LINE_WRAPPING_NONE;
@ -450,8 +470,6 @@ private:
void _update_wrap_at_column(bool p_force = false); void _update_wrap_at_column(bool p_force = false);
void _update_caret_wrap_offset();
/* Viewport. */ /* Viewport. */
HScrollBar *h_scroll = nullptr; HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr; VScrollBar *v_scroll = nullptr;
@ -466,6 +484,10 @@ private:
float v_scroll_speed = 80.0; float v_scroll_speed = 80.0;
// Scrolling. // Scrolling.
int first_visible_line = 0;
int first_visible_line_wrap_ofs = 0;
int first_visible_col = 0;
bool scrolling = false; bool scrolling = false;
bool updating_scrolls = false; bool updating_scrolls = false;
@ -583,6 +605,17 @@ protected:
/* Internal API for CodeEdit, pending public API. */ /* Internal API for CodeEdit, pending public API. */
// brace matching // brace matching
struct BraceMatchingData {
int open_match_line = -1;
int open_match_column = -1;
bool open_matching = false;
bool open_mismatch = false;
int close_match_line = -1;
int close_match_column = -1;
bool close_matching = false;
bool close_mismatch = false;
};
bool highlight_matching_braces_enabled = false; bool highlight_matching_braces_enabled = false;
Color brace_mismatch_color; Color brace_mismatch_color;
@ -607,20 +640,20 @@ protected:
/* Text manipulation */ /* Text manipulation */
// Overridable actions // Overridable actions
virtual void _handle_unicode_input_internal(const uint32_t p_unicode); virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret);
virtual void _backspace_internal(); virtual void _backspace_internal(int p_caret);
virtual void _cut_internal(); virtual void _cut_internal(int p_caret);
virtual void _copy_internal(); virtual void _copy_internal(int p_caret);
virtual void _paste_internal(); virtual void _paste_internal(int p_caret);
virtual void _paste_primary_clipboard_internal(); virtual void _paste_primary_clipboard_internal(int p_caret);
GDVIRTUAL1(_handle_unicode_input, int) GDVIRTUAL2(_handle_unicode_input, int, int)
GDVIRTUAL0(_backspace) GDVIRTUAL1(_backspace, int)
GDVIRTUAL0(_cut) GDVIRTUAL1(_cut, int)
GDVIRTUAL0(_copy) GDVIRTUAL1(_copy, int)
GDVIRTUAL0(_paste) GDVIRTUAL1(_paste, int)
GDVIRTUAL0(_paste_primary_clipboard) GDVIRTUAL1(_paste_primary_clipboard, int)
public: public:
/* General overrides. */ /* General overrides. */
@ -696,7 +729,7 @@ public:
void swap_lines(int p_from_line, int p_to_line); void swap_lines(int p_from_line, int p_to_line);
void insert_line_at(int p_at, const String &p_text); void insert_line_at(int p_at, const String &p_text);
void insert_text_at_caret(const String &p_text); void insert_text_at_caret(const String &p_text, int p_caret = -1);
void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column); void remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
@ -705,13 +738,13 @@ public:
Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const; Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const;
// Overridable actions // Overridable actions
void handle_unicode_input(const uint32_t p_unicode); void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1);
void backspace(); void backspace(int p_caret = -1);
void cut(); void cut(int p_caret = -1);
void copy(); void copy(int p_caret = -1);
void paste(); void paste(int p_caret = -1);
void paste_primary_clipboard(); void paste_primary_clipboard(int p_caret = -1);
// Context menu. // Context menu.
PopupMenu *get_menu() const; PopupMenu *get_menu() const;
@ -719,6 +752,10 @@ public:
void menu_option(int p_option); void menu_option(int p_option);
/* Versioning */ /* Versioning */
void start_action(EditAction p_action);
void end_action();
EditAction get_current_action() const;
void begin_complex_operation(); void begin_complex_operation();
void end_complex_operation(); void end_complex_operation();
@ -753,7 +790,7 @@ public:
int get_minimap_line_at_pos(const Point2i &p_pos) const; int get_minimap_line_at_pos(const Point2i &p_pos) const;
bool is_dragging_cursor() const; bool is_dragging_cursor() const;
bool is_mouse_over_selection(bool p_edges = true) const; bool is_mouse_over_selection(bool p_edges = true, int p_caret = -1) const;
/* Caret */ /* Caret */
void set_caret_type(CaretType p_type); void set_caret_type(CaretType p_type);
@ -771,18 +808,30 @@ public:
void set_caret_mid_grapheme_enabled(const bool p_enabled); void set_caret_mid_grapheme_enabled(const bool p_enabled);
bool is_caret_mid_grapheme_enabled() const; bool is_caret_mid_grapheme_enabled() const;
bool is_caret_visible() const; void set_multiple_carets_enabled(bool p_enabled);
Point2 get_caret_draw_pos() const; bool is_multiple_carets_enabled() const;
void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0); int add_caret(int p_line, int p_col);
int get_caret_line() const; void remove_caret(int p_caret);
void remove_secondary_carets();
void merge_overlapping_carets();
int get_caret_count() const;
void set_caret_column(int p_col, bool p_adjust_viewport = true); Vector<int> get_caret_index_edit_order();
int get_caret_column() const; void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
int get_caret_wrap_index() const; bool is_caret_visible(int p_caret = 0) const;
Point2 get_caret_draw_pos(int p_caret = 0) const;
String get_word_under_caret() const; void set_caret_line(int p_line, bool p_adjust_viewport = true, bool p_can_be_hidden = true, int p_wrap_index = 0, int p_caret = 0);
int get_caret_line(int p_caret = 0) const;
void set_caret_column(int p_col, bool p_adjust_viewport = true, int p_caret = 0);
int get_caret_column(int p_caret = 0) const;
int get_caret_wrap_index(int p_caret = 0) const;
String get_word_under_caret(int p_caret = -1) const;
/* Selection. */ /* Selection. */
void set_selecting_enabled(const bool p_enabled); void set_selecting_enabled(const bool p_enabled);
@ -797,27 +846,27 @@ public:
void set_override_selected_font_color(bool p_override_selected_font_color); void set_override_selected_font_color(bool p_override_selected_font_color);
bool is_overriding_selected_font_color() const; bool is_overriding_selected_font_color() const;
void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1); void set_selection_mode(SelectionMode p_mode, int p_line = -1, int p_column = -1, int p_caret = 0);
SelectionMode get_selection_mode() const; SelectionMode get_selection_mode() const;
void select_all(); void select_all();
void select_word_under_caret(); void select_word_under_caret(int p_caret = -1);
void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column); void select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret = 0);
bool has_selection() const; bool has_selection(int p_caret = -1) const;
String get_selected_text() const; String get_selected_text(int p_caret = -1);
int get_selection_line() const; int get_selection_line(int p_caret = 0) const;
int get_selection_column() const; int get_selection_column(int p_caret = 0) const;
int get_selection_from_line() const; int get_selection_from_line(int p_caret = 0) const;
int get_selection_from_column() const; int get_selection_from_column(int p_caret = 0) const;
int get_selection_to_line() const; int get_selection_to_line(int p_caret = 0) const;
int get_selection_to_column() const; int get_selection_to_column(int p_caret = 0) const;
void deselect(); void deselect(int p_caret = -1);
void delete_selection(); void delete_selection(int p_caret = -1);
/* Line wrapping. */ /* Line wrapping. */
void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode); void set_line_wrapping_mode(LineWrappingMode p_wrapping_mode);
@ -866,8 +915,8 @@ public:
int get_total_visible_line_count() const; int get_total_visible_line_count() const;
// Auto Adjust // Auto Adjust
void adjust_viewport_to_caret(); void adjust_viewport_to_caret(int p_caret = 0);
void center_viewport_to_caret(); void center_viewport_to_caret(int p_caret = 0);
// Minimap // Minimap
void set_draw_minimap(bool p_enabled); void set_draw_minimap(bool p_enabled);
@ -949,6 +998,7 @@ public:
TextEdit(const String &p_placeholder = String()); TextEdit(const String &p_placeholder = String());
}; };
VARIANT_ENUM_CAST(TextEdit::EditAction);
VARIANT_ENUM_CAST(TextEdit::CaretType); VARIANT_ENUM_CAST(TextEdit::CaretType);
VARIANT_ENUM_CAST(TextEdit::LineWrappingMode); VARIANT_ENUM_CAST(TextEdit::LineWrappingMode);
VARIANT_ENUM_CAST(TextEdit::SelectionMode); VARIANT_ENUM_CAST(TextEdit::SelectionMode);

File diff suppressed because it is too large Load diff