diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs index aa6bd5740b..2e8e145ef8 100644 --- a/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/Platform/Input/AndroidInputMethod.cs @@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.Input _inputConnection.IsInUpdate = true; - var selection = Client.ActualSelection; + var selection = Client.Selection; var composition = _inputConnection.EditBuffer.HasComposition ? _inputConnection.EditBuffer.Composition!.Value : new TextSelection(-1,-1); diff --git a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs index 3c7e13f8ef..0fd6a399e0 100644 --- a/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs +++ b/src/Android/Avalonia.Android/Platform/Input/AvaloniaInputConnection.cs @@ -279,7 +279,7 @@ namespace Avalonia.Android.Platform.Input public ICharSequence? GetSelectedTextFormatted([GeneratedEnum] GetTextFlags flags) { - return new Java.Lang.String(_editBuffer.SelectedText ?? ""); + return _editBuffer.SelectedText; } public ICharSequence? GetTextAfterCursorFormatted(int n, [GeneratedEnum] GetTextFlags flags) diff --git a/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs b/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs index 1490fdffd3..b052475c24 100644 --- a/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs +++ b/src/Android/Avalonia.Android/Platform/Input/TextEditBuffer.cs @@ -24,14 +24,14 @@ namespace Avalonia.Android.Platform.Input { get { - var selection = _textInputMethod.Client?.ActualSelection ?? default; + var selection = _textInputMethod.Client?.Selection ?? default; return new TextSelection(Math.Min(selection.Start, selection.End), Math.Max(selection.Start, selection.End)); } set { if (_textInputMethod.Client is { } client) - client.ActualSelection = value; + client.Selection = value; } } @@ -51,18 +51,18 @@ namespace Avalonia.Android.Platform.Input } } - public string? SelectedText + public Java.Lang.String? SelectedText { get { if(_textInputMethod.Client is not { } client || Selection.Start < 0 || Selection.End >= client.Text.Length) { - return string.Empty; + return new Java.Lang.String(); } var selection = Selection; - return Text.Substring(selection.Start, selection.End - selection.Start); + return new Java.Lang.String(Text.Substring(selection.Start, selection.End - selection.Start)); } } @@ -105,14 +105,23 @@ namespace Avalonia.Android.Platform.Input } } - internal Java.Lang.ICharSequence? GetTextBeforeCursor(int n) => new Java.Lang.String(_textInputMethod.Client?.GetTextBeforeCaret(n) ?? string.Empty); - internal Java.Lang.ICharSequence? GetTextAfterCursor(int n) => new Java.Lang.String(_textInputMethod.Client?.GetTextAfterCaret(n) ?? string.Empty); + internal Java.Lang.ICharSequence? GetTextBeforeCursor(int n) + { + var start = Math.Max(Selection.Start - n, 0); + var length = Math.Min(n, Selection.Start); + return new Java.Lang.String(_textInputMethod.Client?.GetTextInRange(start, length).ToArray() ?? Array.Empty()); + } + + internal Java.Lang.ICharSequence? GetTextAfterCursor(int n) + { + return new Java.Lang.String(_textInputMethod.Client?.GetTextInRange(Selection.End, n).ToArray() ?? Array.Empty()); + } internal void Remove(int index, int length) { if (_textInputMethod.Client is { } client) { - client.ActualSelection = new TextSelection(index, index + length); + client.Selection = new TextSelection(index, index + length); if (length > 0) _textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); } @@ -126,12 +135,12 @@ namespace Avalonia.Android.Platform.Input var realEnd = Math.Max(start, end); if (realEnd > realStart) { - client.ActualSelection = new TextSelection(realStart, realEnd); + client.Selection = new TextSelection(realStart, realEnd); _textInputMethod?.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); } _topLevel.TextInput(text); var index = realStart + text.Length; - client.ActualSelection = new TextSelection(index, index); + client.Selection = new TextSelection(index, index); Composition = null; } } diff --git a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs index 5a07e49ba7..c6745f6062 100644 --- a/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/TextInputMethodClient.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media.TextFormatting; namespace Avalonia.Input.TextInput { @@ -64,7 +65,7 @@ namespace Avalonia.Input.TextInput /// /// Gets or sets the curent selection range within current surrounding text. /// - public abstract TextSelection Selection { get; set; } + public abstract TextSelection SelectionInSurroundingText { get; set; } /// /// Sets the non-committed input string @@ -122,10 +123,9 @@ namespace Avalonia.Input.TextInput ResetRequested?.Invoke(this, EventArgs.Empty); } - internal virtual string GetTextBeforeCaret(int length) => string.Empty; - internal virtual string GetTextAfterCaret(int length) => string.Empty; + internal virtual ReadOnlySpan GetTextInRange(int start, int length) => Span.Empty; - internal virtual TextSelection ActualSelection { get => Selection; set => Selection = value; } + internal virtual TextSelection Selection { get => SelectionInSurroundingText; set => SelectionInSurroundingText = value; } } public record struct TextSelection(int Start, int End); diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index d332ee2b30..6b04b262c4 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -48,97 +48,15 @@ namespace Avalonia.Controls internal override string Text => _presenter?.Text ?? string.Empty; - internal override string GetTextBeforeCaret(int length) + internal override ReadOnlySpan GetTextInRange(int start, int length) { if (_presenter is null || _parent is null) { - return ""; + return base.GetTextInRange(start, length); } - var selectionStart = _presenter.SelectionStart; - - var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(selectionStart, false); - - var textLine = _presenter.TextLayout.TextLines[lineIndex]; - - var offset = selectionStart - textLine.FirstTextSourceIndex; - - var currentLineLength = Math.Min(offset, length); - var start = Math.Max(offset - currentLineLength, 0); - - var lineText = GetTextLineText(textLine); - var text = lineText.Substring(start, currentLineLength); - - var newText = text; - - length -= currentLineLength; - - while (length > 0) - { - lineIndex--; - if (lineIndex >= 0) - { - textLine = _presenter.TextLayout.TextLines[lineIndex]; - currentLineLength = Math.Min(textLine.Length, length); - - lineText = GetTextLineText(textLine); - text = lineText.Substring(textLine.Length - currentLineLength, currentLineLength); - - newText = text + newText; - - length -= currentLineLength; - } - else - break; - } - - return newText; - } - - internal override string GetTextAfterCaret(int length) - { - if (_presenter is null || _parent is null) - { - return ""; - } - - var selectionEnd = _presenter.SelectionStart; - - var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(selectionEnd, false); - - var textLine = _presenter.TextLayout.TextLines[lineIndex]; - var lastIndex = textLine.FirstTextSourceIndex + textLine.Length; - - var currentLineLength = Math.Min(lastIndex - selectionEnd, length); - var start = Math.Max(selectionEnd - textLine.FirstTextSourceIndex, 0); - - var builder = new StringBuilder(); - - var lineText = GetTextLineText(textLine); - - builder.Append(lineText.Substring(start, currentLineLength)); - - length -= currentLineLength; - - while (length > 0) - { - lineIndex++; - if (lineIndex < _presenter.TextLayout.TextLines.Count) - { - textLine = _presenter.TextLayout.TextLines[lineIndex]; - currentLineLength = Math.Min(textLine.Length, length); - - lineText = GetTextLineText(textLine); - var text = lineText.Substring(0, currentLineLength); - - builder.Append(text); - - length -= currentLineLength; - } - else - break; - } - - return builder.ToString(); + var end = Math.Max(0 ,Math.Min(start + length, Text.Length - 1)); + start = Math.Max(start, 0); + return Text.AsSpan().Slice(start, end - start); } public override Rect CursorRectangle @@ -161,7 +79,7 @@ namespace Avalonia.Controls } } - public override TextSelection Selection + public override TextSelection SelectionInSurroundingText { get { @@ -205,7 +123,7 @@ namespace Avalonia.Controls } } - internal override TextSelection ActualSelection + internal override TextSelection Selection { get { diff --git a/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs b/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs index a3abccd2b4..8f209de428 100644 --- a/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs +++ b/src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs @@ -97,7 +97,7 @@ namespace Avalonia.Native } var surroundingText = _client.SurroundingText; - var selection = _client.Selection; + var selection = _client.SelectionInSurroundingText; _inputMethod.SetSurroundingText( surroundingText ?? "", @@ -137,7 +137,7 @@ namespace Avalonia.Native { if (_client.SupportsSurroundingText) { - _client.Selection = new TextSelection(start, end); + _client.SelectionInSurroundingText = new TextSelection(start, end); } } } diff --git a/src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs b/src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs index 11722851b7..f1bb0d22bd 100644 --- a/src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs +++ b/src/Browser/Avalonia.Browser/BrowserTextInputMethod.cs @@ -47,7 +47,7 @@ internal class BrowserTextInputMethod( ShowIme(); var surroundingText = _client.SurroundingText ?? ""; - var selection = _client.Selection; + var selection = _client.SelectionInSurroundingText; InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End); } @@ -76,7 +76,7 @@ internal class BrowserTextInputMethod( if (_client != null) { var surroundingText = _client.SurroundingText ?? ""; - var selection = _client.Selection; + var selection = _client.SelectionInSurroundingText; InputHelper.SetSurroundingText(_inputElement, surroundingText, selection.Start, selection.End); } @@ -86,7 +86,7 @@ internal class BrowserTextInputMethod( { InputHelper.FocusElement(_inputElement); InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, - _client?.Selection.End ?? 0); + _client?.SelectionInSurroundingText.End ?? 0); InputHelper.FocusElement(_inputElement); } @@ -118,7 +118,7 @@ internal class BrowserTextInputMethod( if (start != -1 && end != -1 && _client != null) { - _client.Selection = new TextSelection(start, end); + _client.SelectionInSurroundingText = new TextSelection(start, end); } } diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 65827607c1..6103a206fe 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -298,7 +298,7 @@ namespace Avalonia.Win32.Input { Client.SetPreeditText(null); - if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End) + if (Client.SupportsSurroundingText && Client.SelectionInSurroundingText.Start != Client.SelectionInSurroundingText.End) { KeyPress(Key.Delete, PhysicalKey.Delete); } diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs index 5a83f48213..e26658ce35 100644 --- a/src/iOS/Avalonia.iOS/TextInputResponder.cs +++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs @@ -231,7 +231,7 @@ partial class AvaloniaView } else { - var currentSelection = _client.Selection; + var currentSelection = _client.SelectionInSurroundingText; var span = new CombinedSpan3(surroundingText.AsSpan(0, currentSelection.Start), _markedText, surroundingText.AsSpan(currentSelection.Start, currentSelection.End - currentSelection.Start)); @@ -249,7 +249,7 @@ partial class AvaloniaView var r = (AvaloniaTextRange)range; Logger.TryGet(LogEventLevel.Debug, ImeLog)? .Log(null, "IUIKeyInput.ReplaceText {start} {end} {text}", r.StartIndex, r.EndIndex, text); - _client.Selection = new TextSelection(r.StartIndex, r.EndIndex); + _client.SelectionInSurroundingText = new TextSelection(r.StartIndex, r.EndIndex); TextInput(text); } @@ -470,19 +470,19 @@ partial class AvaloniaView { get { - return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.End); + return new AvaloniaTextRange(_client.SelectionInSurroundingText.Start, _client.SelectionInSurroundingText.End); } set { if (_inSurroundingTextUpdateEvent > 0) return; if (value == null) - _client.Selection = default; + _client.SelectionInSurroundingText = default; else { var r = (AvaloniaTextRange)value; - _client.Selection = new TextSelection(r.StartIndex, r.EndIndex); + _client.SelectionInSurroundingText = new TextSelection(r.StartIndex, r.EndIndex); } } } @@ -504,7 +504,7 @@ partial class AvaloniaView { if (string.IsNullOrWhiteSpace(_markedText)) return null!; - return new AvaloniaTextRange(_client.Selection.Start, _client.Selection.Start + _markedText.Length); + return new AvaloniaTextRange(_client.SelectionInSurroundingText.Start, _client.SelectionInSurroundingText.Start + _markedText.Length); } }