Răsfoiți Sursa

Merge pull request #61902 from Paulb23/multi-caret

Add Multi-caret support to TextEdit
Rémi Verschelde 3 ani în urmă
părinte
comite
f5903215d0

+ 127 - 1
doc/classes/TextEdit.xml

@@ -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="merge_overlapping_carets">
+			<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">
 		<method name="paste">
 			<return type="void" />
 			<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>

+ 429 - 239
editor/code_editor.cpp

@@ -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_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column());
+		selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0));
+		selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0));
 	}
 	}
 
 
 	String replace_text = get_replace_text();
 	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_column(selection_begin.height);
+		text_editor->set_caret_line(selection_begin.width, false, true, 0, 0);
+		text_editor->set_caret_column(selection_begin.height, true, 0);
 	}
 	}
 
 
 	if (search_current()) {
 	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_end = Point2i(text_editor->get_selection_to_line(), text_editor->get_selection_to_column());
+		selection_begin = Point2i(text_editor->get_selection_from_line(0), text_editor->get_selection_from_column(0));
+		selection_end = Point2i(text_editor->get_selection_to_line(0), text_editor->get_selection_to_column(0));
 	}
 	}
 
 
 	int vsval = text_editor->get_v_scroll();
 	int vsval = text_editor->get_v_scroll();
 
 
-	text_editor->set_caret_line(0);
-	text_editor->set_caret_column(0);
+	text_editor->set_caret_line(0, false, true, 0, 0);
+	text_editor->set_caret_column(0, true, 0);
 
 
 	String replace_text = get_replace_text();
 	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_column(selection_begin.height);
+		text_editor->set_caret_line(selection_begin.width, false, true, 0, 0);
+		text_editor->set_caret_column(selection_begin.height, true, 0);
 	}
 	}
 	if (search_current()) {
 	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_column(orig_cursor.y);
+	text_editor->set_caret_line(orig_cursor.x, false, true, 0, 0);
+	text_editor->set_caret_column(orig_cursor.y, true, 0);
 
 
 	if (selection_enabled && is_selection_only()) {
 	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_col = text_editor->get_caret_column();
+	r_line = text_editor->get_caret_line(0);
+	r_col = text_editor->get_caret_column(0);
 
 
-	if (text_editor->has_selection() && is_selection_only()) {
+	if (text_editor->has_selection(0) && is_selection_only()) {
 		return;
 		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()) {
-		search_text->set_text(text_editor->get_selected_text());
+	if (text_editor->has_selection(0) && !selection_only->is_pressed()) {
+		search_text->set_text(text_editor->get_selected_text(0));
 	}
 	}
 
 
 	if (!get_search_text().is_empty()) {
 	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();
-	int cursor_column = text_editor->get_caret_column();
+	Vector<int> cursor_columns;
+	cursor_columns.resize(text_editor->get_caret_count());
+	for (int c = 0; c < text_editor->get_caret_count(); c++) {
+		cursor_columns.write[c] = text_editor->get_caret_column(c);
+	}
 
 
 	bool changed_indentation = false;
 	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) {
-					cursor_column += indent_size - 1;
+				for (int c = 0; c < text_editor->get_caret_count(); c++) {
+					if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) {
+						cursor_columns.write[c] += indent_size - 1;
+					}
 				}
 				}
 				line = line.left(j) + indent + line.substr(j + 1);
 				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();
-	int cursor_column = text_editor->get_caret_column();
+	Vector<int> cursor_columns;
+	cursor_columns.resize(text_editor->get_caret_count());
+	for (int c = 0; c < text_editor->get_caret_count(); c++) {
+		cursor_columns.write[c] = text_editor->get_caret_column(c);
+	}
 
 
 	bool changed_indentation = false;
 	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) {
-						cursor_column -= indent_size;
+					for (int c = 0; c < text_editor->get_caret_count(); c++) {
+						if (text_editor->get_caret_line(c) == i && text_editor->get_caret_column(c) > j) {
+							cursor_columns.write[c] -= indent_size;
+						}
 					}
 					}
 					line = line.left(j - indent_size) + "\t" + line.substr(j + 1);
 					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();
-	int end = text_editor->get_selection_to_line();
-	int begin_col = text_editor->get_selection_from_column();
-	int end_col = text_editor->get_selection_to_column();
-
-	for (int i = begin; i <= end; i++) {
-		int len = text_editor->get_line(i).length();
-		if (i == end) {
-			len = end_col;
-		}
-		if (i == begin) {
-			len -= begin_col;
+	Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
+	for (const int &c : caret_edit_order) {
+		if (!text_editor->has_selection(c)) {
+			continue;
 		}
 		}
-		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;
-		}
+		int begin = text_editor->get_selection_from_line(c);
+		int end = text_editor->get_selection_to_line(c);
+		int begin_col = text_editor->get_selection_from_column(c);
+		int end_col = text_editor->get_selection_to_column(c);
 
 
-		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);
+		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);
 		}
 		}
-		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++) {
-			int line_id = i;
-			int next_id = i - 1;
+	Vector<int> carets_to_remove;
+
+	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--) {
-			int line_id = i;
-			int next_id = i + 1;
+	Vector<int> carets_to_remove;
+
+	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);
-		}
-		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->set_caret_line(next_id, c == 0, true, 0, c);
 		}
 		}
+	}
 
 
-		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_column(0);
+		text_editor->set_caret_line(1, p_caret == 0, true, 0, p_caret);
+		text_editor->set_caret_column(0, p_caret == 0, p_caret);
 	}
 	}
-	text_editor->backspace();
+	text_editor->backspace(p_caret);
 	if (p_line < text_editor->get_line_count()) {
 	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);
-		text_editor->deselect();
-		for (int i = 0; i < count; i++) {
-			_delete_line(from_line);
+	Vector<int> carets_to_remove;
+
+	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 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++) {
-		text_editor->unfold_line(i);
-	}
-	text_editor->deselect();
-	text_editor->insert_text_at_caret(new_text);
-	text_editor->set_caret_line(cursor_new_line);
-	text_editor->set_caret_column(cursor_new_column);
-	if (selection_active) {
-		text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column);
-	}
+	Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
+	for (const int &c : caret_edit_order) {
+		const int cursor_column = text_editor->get_caret_column(c);
+		int from_line = text_editor->get_caret_line(c);
+		int to_line = text_editor->get_caret_line(c);
+		int from_column = 0;
+		int to_column = 0;
+		int cursor_new_line = to_line + 1;
+		int cursor_new_column = text_editor->get_caret_column(c);
+		String new_text = "\n" + text_editor->get_line(from_line);
+		bool selection_active = false;
+
+		text_editor->set_caret_column(text_editor->get_line(from_line).length(), c == 0, c);
+		if (text_editor->has_selection(c)) {
+			from_column = text_editor->get_selection_from_column(c);
+			to_column = text_editor->get_selection_to_column(c);
+
+			from_line = text_editor->get_selection_from_line(c);
+			to_line = text_editor->get_selection_to_line(c);
+			cursor_new_line = to_line + text_editor->get_caret_line(c) - from_line;
+			cursor_new_column = to_column == cursor_column ? 2 * to_column - from_column : to_column;
+			new_text = text_editor->get_selected_text(c);
+			selection_active = true;
+
+			text_editor->set_caret_line(to_line, c == 0, true, 0, c);
+			text_editor->set_caret_column(to_column, c == 0, c);
+		}
 
 
+		for (int i = from_line; i <= to_line; i++) {
+			text_editor->unfold_line(i);
+		}
+		text_editor->deselect(c);
+		text_editor->insert_text_at_caret(new_text, c);
+		text_editor->set_caret_line(cursor_new_line, c == 0, true, 0, c);
+		text_editor->set_caret_column(cursor_new_column, c == 0, c);
+		if (selection_active) {
+			text_editor->select(to_line, to_column, 2 * to_line - from_line, to_line == from_line ? 2 * to_column - from_column : to_column, c);
+		}
+	}
+	text_editor->merge_overlapping_carets();
 	text_editor->end_complex_operation();
 	text_editor->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.
-		if (text_editor->get_selection_to_column() == 0) {
-			end -= 1;
-		}
+	Vector<int> caret_edit_order = text_editor->get_caret_index_edit_order();
+	for (const int &c : caret_edit_order) {
+		if (text_editor->has_selection(c)) {
+			int begin = text_editor->get_selection_from_line(c);
+			int end = text_editor->get_selection_to_line(c);
 
 
-		int col_to = text_editor->get_selection_to_column();
-		int cursor_pos = text_editor->get_caret_column();
+			// End of selection ends on the first column of the last line, ignore it.
+			if (text_editor->get_selection_to_column(c) == 0) {
+				end -= 1;
+			}
 
 
-		// 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;
+			int col_to = text_editor->get_selection_to_column(c);
+			int cursor_pos = text_editor->get_caret_column(c);
+
+			// Check if all lines in the selected block are commented.
+			bool is_commented = true;
+			for (int i = begin; i <= end; i++) {
+				if (!text_editor->get_line(i).begins_with(delimiter)) {
+					is_commented = false;
+					break;
+				}
 			}
 			}
-		}
-		for (int i = begin; i <= end; i++) {
-			String line_text = text_editor->get_line(i);
+			for (int i = begin; i <= end; i++) {
+				String line_text = text_editor->get_line(i);
 
 
-			if (line_text.strip_edges().is_empty()) {
-				line_text = delimiter;
-			} else {
-				if (is_commented) {
-					line_text = line_text.substr(delimiter.length(), line_text.length());
+				if (line_text.strip_edges().is_empty()) {
+					line_text = delimiter;
 				} else {
 				} else {
-					line_text = delimiter + line_text;
+					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);
 			}
 			}
-			text_editor->set_line(i, line_text);
-		}
 
 
-		// Adjust selection & cursor position.
-		int offset = (is_commented ? -1 : 1) * delimiter.length();
-		int col_from = text_editor->get_selection_from_column() > 0 ? text_editor->get_selection_from_column() + offset : 0;
+			// Adjust selection & cursor position.
+			int offset = (is_commented ? -1 : 1) * delimiter.length();
+			int col_from = text_editor->get_selection_from_column(c) > 0 ? text_editor->get_selection_from_column(c) + offset : 0;
 
 
-		if (is_commented && text_editor->get_caret_column() == text_editor->get_line(text_editor->get_caret_line()).length() + 1) {
-			cursor_pos += 1;
-		}
+			if (is_commented && text_editor->get_caret_column(c) == text_editor->get_line(text_editor->get_caret_line(c)).length() + 1) {
+				cursor_pos += 1;
+			}
 
 
-		if (text_editor->get_selection_to_column() != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line()).length() + 1) {
-			col_to += offset;
-		}
+			if (text_editor->get_selection_to_column(c) != 0 && col_to != text_editor->get_line(text_editor->get_selection_to_line(c)).length() + 1) {
+				col_to += offset;
+			}
 
 
-		if (text_editor->get_caret_column() != 0) {
-			cursor_pos += offset;
-		}
+			if (text_editor->get_caret_column(c) != 0) {
+				cursor_pos += offset;
+			}
 
 
-		text_editor->select(begin, col_from, text_editor->get_selection_to_line(), col_to);
-		text_editor->set_caret_column(cursor_pos);
+			text_editor->select(begin, col_from, text_editor->get_selection_to_line(c), col_to, c);
+			text_editor->set_caret_column(cursor_pos, c == 0, c);
 
 
-	} else {
-		int begin = text_editor->get_caret_line();
-		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;
-			col += delimiter_length;
-		}
+			int begin = text_editor->get_caret_line(c);
+			String line_text = text_editor->get_line(begin);
+			int delimiter_length = delimiter.length();
+
+			int col = text_editor->get_caret_column(c);
+			if (line_text.begins_with(delimiter)) {
+				line_text = line_text.substr(delimiter_length, line_text.length());
+				col -= delimiter_length;
+			} else {
+				line_text = delimiter + line_text;
+				col += delimiter_length;
+			}
 
 
-		text_editor->set_line(begin, line_text);
-		text_editor->set_caret_column(col);
+			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();
-	text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line));
+	for (int i = 0; i < text_editor->get_caret_count(); i++) {
+		int line = text_editor->get_caret_line(i);
+		text_editor->set_line_as_bookmarked(line, !text_editor->is_line_bookmarked(line));
+	}
 }
 }
 
 
 void CodeTextEditor::goto_next_bookmark() {
 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]);

+ 1 - 1
editor/code_editor.h

@@ -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:

+ 18 - 9
editor/plugins/script_text_editor.cpp

@@ -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();
-		local_pos = tx->get_caret_draw_pos();
+		tx->adjust_viewport_to_caret(0);
+		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");

+ 4 - 3
editor/plugins/text_editor.cpp

@@ -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();
-		tx->adjust_viewport_to_caret();
-		_make_context_menu(tx->has_selection(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos()));
+		int line = tx->get_caret_line(0);
+		tx->adjust_viewport_to_caret(0);
+		_make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0)));
 		context_menu->grab_focus();
 		context_menu->grab_focus();
 	}
 	}
 }
 }

+ 1 - 0
editor/plugins/text_shader_editor.cpp

@@ -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();

+ 398 - 351
scene/gui/code_edit.cpp

@@ -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) {
-	bool had_selection = has_selection();
-	String selection_text = (had_selection ? get_selected_text() : "");
+void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
+	start_action(EditAction::ACTION_TYPING);
+	Vector<int> caret_edit_order = get_caret_index_edit_order();
+	for (const int &i : caret_edit_order) {
+		if (p_caret != -1 && p_caret != i) {
+			continue;
+		}
 
 
-	if (had_selection) {
-		begin_complex_operation();
-		delete_selection();
-	}
+		bool had_selection = has_selection(i);
+		String selection_text = (had_selection ? get_selected_text(i) : "");
 
 
-	// Remove the old character if in overtype mode and no selection.
-	if (is_overtype_mode_enabled() && !had_selection) {
-		begin_complex_operation();
+		if (had_selection) {
+			delete_selection(i);
+		}
 
 
-		/* 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);
+		// Remove the old character if in overtype mode and no selection.
+		if (is_overtype_mode_enabled() && !had_selection) {
+			// Make sure we don't try and remove empty space.
+			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 };
+		const char32_t chr[2] = { (char32_t)p_unicode, 0 };
 
 
-	if (auto_brace_completion_enabled) {
-		int cl = get_caret_line();
-		int cc = get_caret_column();
+		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);
+			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);
-				set_caret_column(get_caret_column() - 1);
-			}
-		} 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);
-			} 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);
+				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 {
 			} else {
-				insert_text_at_caret(chr);
+				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);
+					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);
 			}
 			}
-			set_caret_column(cc + caret_move_offset);
+		} else {
+			insert_text_at_caret(chr, i);
 		}
 		}
-	} 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();
-	int cl = get_caret_line();
+	begin_complex_operation();
+	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) {
-		return;
-	}
+		int cc = get_caret_column(i);
+		int cl = get_caret_line(i);
 
 
-	if (cl > 0 && _is_line_hidden(cl - 1)) {
-		unfold_line(get_caret_line() - 1);
-	}
+		if (cc == 0 && cl == 0) {
+			continue;
+		}
 
 
-	int prev_line = cc ? cl : cl - 1;
-	int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
+		if (cl > 0 && _is_line_hidden(cl - 1)) {
+			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) {
-		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();
+		merge_gutters(prev_line, cl);
 
 
-			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);
+		if (auto_brace_completion_enabled && cc > 0) {
+			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) {
+					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
-	// same way as tabs.
-	if (indent_using_spaces && cc != 0) {
-		if (get_first_non_whitespace_column(cl) >= cc) {
-			prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
-			prev_line = cl;
+		// 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.
+		if (indent_using_spaces && cc != 0) {
+			if (get_first_non_whitespace_column(cl) >= cc) {
+				prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
+				prev_line = cl;
+			}
 		}
 		}
-	}
 
 
-	remove_text(prev_line, prev_column, cl, cc);
+		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);
 
 
-	set_caret_line(prev_line, false, true);
-	set_caret_column(prev_column);
+		adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
+	}
+	merge_overlapping_carets();
+	end_complex_operation();
 }
 }
 
 
 /* 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());
-	if (spaces_to_add > 0) {
-		insert_text_at_caret(String(" ").repeat(spaces_to_add));
+	begin_complex_operation();
+	Vector<int> caret_edit_order = get_caret_index_edit_order();
+	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;
+
+		int start_line = get_caret_line(c);
+		int end_line = start_line;
+		if (has_selection(c)) {
+			start_line = get_selection_from_line(c);
+			end_line = get_selection_to_line(c);
+
+			// Ignore the last line if the selection is not past the first column.
+			if (get_selection_to_column(c) == 0) {
+				selection_offset = 0;
+				end_line--;
+			}
+		}
 
 
-	/* This value informs us by how much we changed selection position by indenting right. */
-	/* Default is 1 for tab indentation.                                                   */
-	int selection_offset = 1;
-
-	int start_line = get_caret_line();
-	int end_line = start_line;
-	if (has_selection()) {
-		start_line = get_selection_from_line();
-		end_line = get_selection_to_line();
+		for (int i = start_line; i <= end_line; i++) {
+			const String line_text = get_line(i);
+			if (line_text.size() == 0 && has_selection(c)) {
+				continue;
+			}
 
 
-		/* Ignore the last line if the selection is not past the first column. */
-		if (get_selection_to_column() == 0) {
-			selection_offset = 0;
-			end_line--;
-		}
-	}
+			if (!indent_using_spaces) {
+				set_line(i, '\t' + line_text);
+				continue;
+			}
 
 
-	for (int i = start_line; i <= end_line; i++) {
-		const String line_text = get_line(i);
-		if (line_text.size() == 0 && has_selection()) {
-			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;
 		}
 		}
 
 
-		if (!indent_using_spaces) {
-			set_line(i, '\t' + line_text);
-			continue;
+		// Fix selection and caret being off after shifting selection right.
+		if (has_selection(c)) {
+			select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c);
 		}
 		}
-
-		/* 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;
+		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();
-	const String &line = get_line(cl);
-
-	if (line[cc - 1] == '\t') {
-		remove_text(cl, cc - 1, cl, cc);
-		set_caret_column(MAX(0, cc - 1));
-		return;
-	}
+	begin_complex_operation();
+	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') {
+			remove_text(cl, cc - 1, cl, cc);
+			set_caret_column(MAX(0, cc - 1), c == 0, c);
+			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) {
-		for (int i = 1; i <= spaces_to_remove; i++) {
-			if (line[cc - i] != ' ') {
-				spaces_to_remove = i - 1;
-				break;
+		int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
+		if (spaces_to_remove > 0) {
+			for (int i = 1; i <= spaces_to_remove; i++) {
+				if (line[cc - i] != ' ') {
+					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                                                      */
-	/* changing content of line can move caret and selection on its own (if new line ends before previous position of either),  */
-	/* therefore we just remember initial values and at the end of the operation offset them by number of removed characters.   */
-	int removed_characters = 0;
-	int initial_selection_end_column = 0;
-	int initial_cursor_column = get_caret_column();
-
-	int start_line = get_caret_line();
-	int end_line = start_line;
-	if (has_selection()) {
-		start_line = get_selection_from_line();
-		end_line = get_selection_to_line();
-
-		/* Ignore the last line if the selection is not past the first column. */
-		initial_selection_end_column = get_selection_to_column();
-		if (initial_selection_end_column == 0) {
-			end_line--;
+	Vector<int> caret_edit_order = get_caret_index_edit_order();
+	for (const int &c : caret_edit_order) {
+		// Moving caret and selection after unindenting can get tricky because
+		// changing content of line can move caret and selection on its own (if new line ends before previous position of either)
+		// therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
+		int removed_characters = 0;
+		int initial_selection_end_column = 0;
+		int initial_cursor_column = get_caret_column(c);
+
+		int start_line = get_caret_line(c);
+		int end_line = start_line;
+		if (has_selection(c)) {
+			start_line = get_selection_from_line(c);
+			end_line = get_selection_to_line(c);
+
+			// Ignore the last line if the selection is not past the first column.
+			initial_selection_end_column = get_selection_to_column(c);
+			if (initial_selection_end_column == 0) {
+				end_line--;
+			}
 		}
 		}
-	}
 
 
-	bool first_line_edited = false;
-	bool last_line_edited = false;
+		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);
+		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());
+			if (line_text.begins_with("\t")) {
+				line_text = line_text.substr(1, line_text.length());
 
 
-			set_line(i, line_text);
-			removed_characters = 1;
+				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;
-		}
+				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());
+			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;
+				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;
+				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);
-		}
+		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(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
+			// 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);
 	}
 	}
-	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. */
-	const int cc = p_split_current_line ? get_caret_column() : get_line(get_caret_line()).length();
-	const int cl = get_caret_line();
-
-	const String line = get_line(cl);
-
-	String ins = "\n";
+	begin_complex_operation();
+	Vector<int> caret_edit_order = get_caret_index_edit_order();
+	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);
 
 
-	/* Append current indentation. */
-	int space_count = 0;
-	int line_col = 0;
-	for (; line_col < cc; line_col++) {
-		if (line[line_col] == '\t') {
-			ins += indent_text;
-			space_count = 0;
-			continue;
-		}
+		const String line = get_line(cl);
 
 
-		if (line[line_col] == ' ') {
-			space_count++;
+		String ins = "\n";
 
 
-			if (space_count == indent_size) {
+		// Append current indentation.
+		int space_count = 0;
+		int line_col = 0;
+		for (; line_col < cc; line_col++) {
+			if (line[line_col] == '\t') {
 				ins += indent_text;
 				ins += indent_text;
 				space_count = 0;
 				space_count = 0;
+				continue;
 			}
 			}
-			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 = ' ';
+			if (line[line_col] == ' ') {
+				space_count++;
 
 
-		for (; line_col < cc; line_col++) {
-			char32_t c = line[line_col];
-			if (auto_indent_prefixes.has(c)) {
-				should_indent = true;
-				indent_char = c;
+				if (space_count == indent_size) {
+					ins += indent_text;
+					space_count = 0;
+				}
 				continue;
 				continue;
 			}
 			}
+			break;
+		}
 
 
-			/* 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 (is_line_folded(cl)) {
+			unfold_line(cl);
 		}
 		}
 
 
-		if (should_indent) {
-			ins += indent_text;
+		// 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 = ' ';
 
 
-			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);
+			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;
 				}
 				}
 			}
 			}
-		}
-	}
 
 
-	begin_complex_operation();
+			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);
+					}
+				}
+			}
+		}
 
 
-	bool first_line = false;
-	if (!p_split_current_line) {
-		deselect();
+		bool first_line = false;
+		if (!p_split_current_line) {
+			deselect(i);
 
 
-		if (p_above) {
-			if (cl > 0) {
-				set_caret_line(cl - 1, false);
-				set_caret_column(get_line(get_caret_line()).length());
+			if (p_above) {
+				if (cl > 0) {
+					set_caret_line(cl - 1, false, true, 0, i);
+					set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
+				} else {
+					set_caret_column(0, i == 0, i);
+					first_line = true;
+				}
 			} else {
 			} else {
-				set_caret_column(0);
-				first_line = true;
+				set_caret_column(line.length(), i == 0, i);
 			}
 			}
-		} else {
-			set_caret_column(line.length());
 		}
 		}
-	}
 
 
-	insert_text_at_caret(ins);
+		insert_text_at_caret(ins, i);
 
 
-	if (first_line) {
-		set_caret_line(0);
-	} else if (brace_indent) {
-		set_caret_line(get_caret_line() - 1, false);
-		set_caret_column(get_line(get_caret_line()).length());
+		if (first_line) {
+			set_caret_line(0, i == 0, true, 0, i);
+		} else if (brace_indent) {
+			set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
+			set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
+		}
 	}
 	}
 
 
 	end_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. */
-	if (has_selection()) {
-		if (_is_line_hidden(get_selection_from_line()) && _is_line_hidden(get_selection_to_line())) {
-			deselect();
-		} else if (_is_line_hidden(get_selection_from_line())) {
-			select(p_line, 9999, get_selection_to_line(), get_selection_to_column());
-		} else if (_is_line_hidden(get_selection_to_line())) {
-			select(get_selection_from_line(), get_selection_from_column(), p_line, 9999);
+	for (int i = 0; i < get_caret_count(); i++) {
+		// Fix selection.
+		if (has_selection(i)) {
+			if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) {
+				deselect(i);
+			} else if (_is_line_hidden(get_selection_from_line(i))) {
+				select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i);
+			} else if (_is_line_hidden(get_selection_to_line(i))) {
+				select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i);
+			}
 		}
 		}
-	}
 
 
-	/* Reset caret. */
-	if (_is_line_hidden(get_caret_line())) {
-		set_caret_line(p_line, false, false);
-		set_caret_column(get_line(p_line).length(), false);
+		// Reset caret.
+		if (_is_line_hidden(get_caret_line(i))) {
+			set_caret_line(p_line, false, false, 0, i);
+			set_caret_column(get_line(p_line).length(), false, i);
+		}
 	}
 	}
+
+	merge_overlapping_carets();
 	queue_redraw();
 	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);
+
+		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;
+
+		if (p_replace) {
+			// 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;
+
+			bool merge_text = true;
+			int in_string = is_in_string(caret_line, caret_col);
+			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;
+				}
+			}
 
 
-	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;
-
-	if (p_replace) {
-		/* Find end of current section */
-		const String line = get_line(caret_line);
-		int caret_col = get_caret_column();
-		int caret_remove_line = caret_line;
-
-		bool merge_text = true;
-		int in_string = is_in_string(caret_line, caret_col);
-		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;
+					}
+				}
 			}
 			}
-		}
 
 
-		if (merge_text) {
-			for (; caret_col < line.length(); caret_col++) {
-				if (is_symbol(line[caret_col])) {
+			// 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;
 					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);
 		}
 		}
 
 
-		/* 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. */
+		//* Handle merging of symbols eg strings, brackets.
 		const String line = get_line(caret_line);
 		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++;
+		char32_t next_char = line[get_caret_column(i)];
+		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];
 
 
-		/* 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(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;
 
 
-	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 (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(), 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 && 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() > 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);
+		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);
+				}
 			}
 			}
 		}
 		}
 	}
 	}
-
 	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);

+ 2 - 2
scene/gui/code_edit.h

@@ -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 _backspace_internal() override;
+	virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) 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)

Fișier diff suprimat deoarece este prea mare
+ 405 - 357
scene/gui/text_edit.cpp


+ 121 - 71
scene/gui/text_edit.h

@@ -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;
-	} caret;
+	};
+
+	// Vector containing all the carets, index '0' is the "main caret" and should never be removed.
+	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;
-		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;
+	SelectionMode selecting_mode = SelectionMode::SELECTION_MODE_NONE;
 
 
 	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 _post_shift_selection();
+	void _pre_shift_selection(int p_caret);
+	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 _backspace_internal();
+	virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret);
+	virtual void _backspace_internal(int p_caret);
 
 
-	virtual void _cut_internal();
-	virtual void _copy_internal();
-	virtual void _paste_internal();
-	virtual void _paste_primary_clipboard_internal();
+	virtual void _cut_internal(int p_caret);
+	virtual void _copy_internal(int p_caret);
+	virtual void _paste_internal(int p_caret);
+	virtual void _paste_primary_clipboard_internal(int p_caret);
 
 
-	GDVIRTUAL1(_handle_unicode_input, int)
-	GDVIRTUAL0(_backspace)
-	GDVIRTUAL0(_cut)
-	GDVIRTUAL0(_copy)
-	GDVIRTUAL0(_paste)
-	GDVIRTUAL0(_paste_primary_clipboard)
+	GDVIRTUAL2(_handle_unicode_input, int, int)
+	GDVIRTUAL1(_backspace, int)
+	GDVIRTUAL1(_cut, int)
+	GDVIRTUAL1(_copy, int)
+	GDVIRTUAL1(_paste, int)
+	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 backspace();
+	void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1);
+	void backspace(int p_caret = -1);
 
 
-	void cut();
-	void copy();
-	void paste();
-	void paste_primary_clipboard();
+	void cut(int p_caret = -1);
+	void copy(int p_caret = -1);
+	void paste(int p_caret = -1);
+	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;
-	Point2 get_caret_draw_pos() const;
+	void set_multiple_carets_enabled(bool p_enabled);
+	bool is_multiple_carets_enabled() const;
+
+	int add_caret(int p_line, int p_col);
+	void remove_caret(int p_caret);
+	void remove_secondary_carets();
+	void merge_overlapping_carets();
+	int get_caret_count() const;
+
+	Vector<int> get_caret_index_edit_order();
+	void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col);
+
+	bool is_caret_visible(int p_caret = 0) const;
+	Point2 get_caret_draw_pos(int p_caret = 0) 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 get_caret_line() 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 get_caret_column() 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() const;
+	int get_caret_wrap_index(int p_caret = 0) const;
 
 
-	String get_word_under_caret() 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(int p_from_line, int p_from_column, int p_to_line, int p_to_column);
+	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, 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_column() const;
+	int get_selection_line(int p_caret = 0) const;
+	int get_selection_column(int p_caret = 0) const;
 
 
-	int get_selection_from_line() const;
-	int get_selection_from_column() const;
-	int get_selection_to_line() const;
-	int get_selection_to_column() const;
+	int get_selection_from_line(int p_caret = 0) const;
+	int get_selection_from_column(int p_caret = 0) const;
+	int get_selection_to_line(int p_caret = 0) const;
+	int get_selection_to_column(int p_caret = 0) const;
 
 
-	void deselect();
-	void delete_selection();
+	void deselect(int p_caret = -1);
+	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 center_viewport_to_caret();
+	void adjust_viewport_to_caret(int p_caret = 0);
+	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);

Fișier diff suprimat deoarece este prea mare
+ 356 - 105
tests/scene/test_text_edit.h


Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff