Repository: AvaloniaUI/Avalonia

Issue:
TextBlock unexpectedly deletes first character when using input method auto-complete
### Describe the bug

When using the `auto-complete` feature of an input method (IME) with a `TextBlock` control, the first English character at the beginning of a line is unexpectedly deleted. This behavior occurs consistently and affects the user experience.

### To Reproduce

1. Set up a TextBlock control in your application.
2. Enable an input method that supports auto-complete (e.g., Gboard).
3. Begin typing at the start of a line, using the IME's auto-complete feature.
4. Observe that the first English character is deleted after auto-complete is applied.

### Expected behavior

The TextBlock should maintain all characters, including the first one, when using IME `auto-complete`.

### Avalonia version

11.1.2

### OS

Android

### Additional context



Code
```
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="Base &lt;TextBox Width=&quot;200&quot; Height=&quot;50&quot; /&gt;"/>
        <StackPanel Background="Aqua">
            <TextBlock Text="Base With Height and Width attributes" />
            <TextBox Width="200" Height="50" />
        </StackPanel>
        <StackPanel Background="Chocolate">
            <TextBlock Text="TextInputOptions.ReturnKeyType=&quot;Return&quot;" />
            <TextBox Width="200"
                     Height="50"
                     TextInputOptions.ReturnKeyType="Return" />
        </StackPanel>
        <StackPanel Background="Crimson">
            <TextBlock Text="AcceptsReturn=&quot;True" />
            <TextBox Width="200"
                     Height="50"
                     AcceptsReturn="True" />
        </StackPanel>
        <StackPanel Background="DarkOrange">
            <TextBlock Text="AcceptsReturn=&quot;True&quot; TextInputOptions.ReturnKeyType=&quot;Return&quot;" />
            <TextBox Width="200"
                     Height="50"
                     AcceptsReturn="True"
                     TextInputOptions.ReturnKeyType="Return" />
        </StackPanel>
    </StackPanel>
```



Hints:
Could you test with https://github.com/AvaloniaUI/Avalonia/pull/16666
> Could you test with #16666

``<PackageReference Include="Avalonia.Android" Version="11.2.999-cibuild0051177-alpha" />``

![image](https://github.com/user-attachments/assets/ee0800e2-0ca0-48a3-8485-212d14c9374c)

I tested it on my Android 10 device and my first letter still gets eaten.

- [x] Solution Cleaned
- [x] Packges Download from https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/results?buildId=51177
try this 11.2.999-cibuild0051149-alpha
> Try this 11.2.999-cibuild0051149-alpha

I tested it on my Android 10 device`Gboard` and my first letter still gets eaten.
But [Sogou Pinyin](https://en.wikipedia.org/wiki/Sogou_Pinyin) works well. I don't know how it can be. But Google develops Gboad, it is more likely to adopt the standard.
What's your GBoard version? I've tested many versions across many android versions(OS 6,9, 10, 11, 13, 14), only the AOSP keyboard(not GBoard) on stock Android 10 has an issue with not sending composing regions which is fixed in the linked pr. GBoard itself works fine.
> What's your GBoard version? I've tested many versions across many android versions(OS 6,9, 10, 11, 13, 14), only the AOSP keyboard(not GBoard) on stock Android 10 has an issue with not sending composing regions which is fixed in the linked pr. GBoard itself works fine.

14.4.04.646482735-release-arm64-v8a
System: Android 10 with MIUI 12.5.1 (MIX2S)
I've been able to reproduce the issue. Currently debugging it, and it's a weird issue as it only occurs on the first word.
Could you test with this?
11.2.999-cibuild0051267-alpha
Fixed, thank you, bro. If needed, I will test it on more devices.
The fix isn't merged yet.

Patch:
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index 0ff857985a4..5d35124c69a 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -180,6 +180,11 @@ public override void ShowInputPanel()
 
         private static string GetTextLineText(TextLine textLine)
         {
+            if (textLine.Length == 0)
+            {
+                return string.Empty;
+            }
+
             var builder = StringBuilderCache.Acquire(textLine.Length);
 
             foreach (var run in textLine.TextRuns)


Test patch:
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 42e42dd9f6e..30f82533cea 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -9,11 +9,10 @@
 using Avalonia.Headless;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
+using Avalonia.Input.TextInput;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Rendering.Composition;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
 using Moq;
@@ -1492,6 +1491,30 @@ public void Should_Throw_ArgumentOutOfRange()
             }
         }
 
+        [Fact]
+        public void InputMethodClient_SurroundingText_Returns_Empty_For_Empty_Line()
+        {
+            using var _ = UnitTestApplication.Start(Services);
+
+            var textBox = new TextBox
+            {
+                Template = CreateTemplate(),
+                Text = "",
+                CaretIndex = 0
+            };
+            textBox.ApplyTemplate();
+
+            var eventArgs = new TextInputMethodClientRequestedEventArgs
+            {
+                RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
+            };
+            textBox.RaiseEvent(eventArgs);
+
+            var client = eventArgs.Client;
+            Assert.NotNull(client);
+            Assert.Equal(string.Empty, client.SurroundingText);
+        }
+  
         [Fact]
         public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Same_Line()
         {