Repository: AvaloniaUI/Avalonia

Issue:
Backspace incorrectly places caret before linebreak when deleting the last character of a line
### Describe the bug

The following video is a single Backspace key press followed by an Enter key press:

https://github.com/user-attachments/assets/51b7adad-bc45-42ad-b148-7397667311c0

Notice that backspace deletes 'b' and moves the caret to the line before. Only 'b' should be deleted, and the caret moved at the start of the current line.

### To Reproduce

Delete the last character of a line in a multi-line textbox.

### Expected behavior

The caret should stay on the current line.

### Avalonia version

master 228ecc5003c77dd74d2f037eb69daa4102312015

### OS

_No response_

### Additional context

Caused by #16135


Hints:

Patch:
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index a627c15765b..b12c23c5975 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -1468,7 +1468,7 @@ protected override void OnKeyDown(KeyEventArgs e)
 
                                     SetCurrentValue(CaretIndexProperty, start);
 
-                                    _presenter.MoveCaretToTextPosition(start, true);
+                                    _presenter.MoveCaretToTextPosition(start);
                                 }
                             }
 


Test patch:
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 1cd42a6c6a1..42e42dd9f6e 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -1492,6 +1492,43 @@ public void Should_Throw_ArgumentOutOfRange()
             }
         }
 
+        [Fact]
+        public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Same_Line()
+        {
+            using var _ = UnitTestApplication.Start(Services);
+
+            var textBox = new TextBox
+            {
+                Template = CreateTemplate(),
+                Text = "a\nb",
+                CaretIndex = 3
+            };
+            textBox.ApplyTemplate();
+
+            var topLevel = new TestTopLevel(CreateMockTopLevelImpl().Object)
+            {
+                Template = CreateTopLevelTemplate(),
+                Content = textBox
+            };
+            topLevel.ApplyTemplate();
+            topLevel.LayoutManager.ExecuteInitialLayoutPass();
+
+            var textPresenter = textBox.FindDescendantOfType<TextPresenter>();
+            Assert.NotNull(textPresenter);
+
+            var oldCaretY = textPresenter.GetCursorRectangle().Top;
+            Assert.NotEqual(0, oldCaretY);
+
+            RaiseKeyEvent(textBox, Key.Back, KeyModifiers.None);
+
+            Assert.Equal("a\n", textBox.Text);
+            Assert.Equal(2, textBox.CaretIndex);
+            Assert.Equal(2, textPresenter.CaretIndex);
+
+            var caretY = textPresenter.GetCursorRectangle().Top;
+            Assert.Equal(oldCaretY, caretY);
+        }
+
         private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
             focusManager: new FocusManager(),
             keyboardDevice: () => new KeyboardDevice(),