From b712ca6cc91fbf3af7dd79857125120c223f162f Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:55:24 +0200 Subject: [PATCH] Fix auto complete not working in middle of text --- .../ViewModels/AutocompleteController.cs | 8 ++++++-- .../ViewModels/MessageComposerViewModel.cs | 19 ++++++++++++------- src/Decho/Views/MessageComposerView.axaml.cs | 19 +++++++++++++++++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Decho/ViewModels/AutocompleteController.cs b/src/Decho/ViewModels/AutocompleteController.cs index 04b1117..6529e80 100644 --- a/src/Decho/ViewModels/AutocompleteController.cs +++ b/src/Decho/ViewModels/AutocompleteController.cs @@ -31,7 +31,9 @@ public sealed class AutocompleteController(IEnumerable pro public int TriggerCharIndex { get; private set; } - public void Update(string text) + public int WordEndIndex { get; private set; } + + public void Update(string text, int caretIndex) { if (string.IsNullOrEmpty(text) || _providers.Count == 0) { @@ -39,7 +41,8 @@ public sealed class AutocompleteController(IEnumerable pro return; } - Match match = TriggerPattern.Match(text); + int clampedCaret = Math.Clamp(caretIndex, 0, text.Length); + Match match = TriggerPattern.Match(text[..clampedCaret]); if (!match.Success) { Reset(); @@ -58,6 +61,7 @@ public sealed class AutocompleteController(IEnumerable pro ActiveProvider = provider; FilterText = filter; TriggerCharIndex = match.Index; + WordEndIndex = match.Index + match.Length; List items = provider.ItemsSource().ToList(); List filtered = items diff --git a/src/Decho/ViewModels/MessageComposerViewModel.cs b/src/Decho/ViewModels/MessageComposerViewModel.cs index 78ae5e9..ecabf44 100644 --- a/src/Decho/ViewModels/MessageComposerViewModel.cs +++ b/src/Decho/ViewModels/MessageComposerViewModel.cs @@ -106,8 +106,6 @@ public sealed class MessageComposerViewModel : ViewModelBase (draft, hasFiles, isReadOnly) => (!string.IsNullOrWhiteSpace(draft) || hasFiles) && !isReadOnly); SendCommand = ReactiveCommand.Create(Send, canSend); - - _ = this.WhenAnyValue(x => x.Draft).Subscribe(OnDraftChanged); } public void UpdateAvailableUsers(IEnumerable users) @@ -179,17 +177,24 @@ public sealed class MessageComposerViewModel : ViewModelBase return _commandHandler?.IsCommand(input) ?? input.StartsWith('/'); } - public void InsertAutocomplete(string item) + public int InsertAutocomplete(string item) { string? insertion = Autocomplete.GetInsertion(item); if (insertion is null) { - return; + return -1; } string before = Draft[..Autocomplete.TriggerCharIndex]; - Draft = $"{before}{insertion}"; + string after = Draft[Autocomplete.WordEndIndex..]; + if (after.Length > 0 && char.IsWhiteSpace(after[0])) + { + insertion = insertion.TrimEnd(); + } + + Draft = $"{before}{insertion}{after}"; Autocomplete.Reset(); + return before.Length + insertion.Length; } public void ClearReplyTarget() @@ -226,8 +231,8 @@ public sealed class MessageComposerViewModel : ViewModelBase : string.Empty; } - private void OnDraftChanged(string? draft) + public void UpdateAutocomplete(string text, int caretIndex) { - Autocomplete.Update(draft ?? string.Empty); + Autocomplete.Update(text, caretIndex); } } \ No newline at end of file diff --git a/src/Decho/Views/MessageComposerView.axaml.cs b/src/Decho/Views/MessageComposerView.axaml.cs index 08e5f56..f9d2162 100644 --- a/src/Decho/Views/MessageComposerView.axaml.cs +++ b/src/Decho/Views/MessageComposerView.axaml.cs @@ -1,3 +1,4 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -29,6 +30,11 @@ public partial class MessageComposerView : UserControl // Intercept Enter before the TextBox processes it (AcceptsReturn=True would insert a newline) MessageTextBox.AddHandler(InputElement.KeyDownEvent, OnTextBoxTunnelingKeyDown, RoutingStrategies.Tunnel); + MessageTextBox.GetObservable(TextBox.TextProperty) + .Subscribe(_ => UpdateAutocomplete(MessageTextBox.Text ?? string.Empty, MessageTextBox.CaretIndex)); + MessageTextBox.GetObservable(TextBox.CaretIndexProperty) + .Subscribe(_ => UpdateAutocomplete(MessageTextBox.Text ?? string.Empty, MessageTextBox.CaretIndex)); + _spellChecker = new TextBoxSpellChecker(SpellCheckerConfig.Create("en_GB")); _spellChecker.Initialize(MessageTextBox); } @@ -47,6 +53,12 @@ public partial class MessageComposerView : UserControl } } + private void UpdateAutocomplete(string text, int caretIndex) + { + MessageComposerViewModel? vm = this.GetDataContext(); + vm?.UpdateAutocomplete(text, caretIndex); + } + private void OnTextBoxKeyDown(object? sender, KeyEventArgs e) { MessageComposerViewModel? vm = this.GetDataContext(); @@ -137,8 +149,11 @@ public partial class MessageComposerView : UserControl return; } - vm.InsertAutocomplete(item); - MessageTextBox.CaretIndex = MessageTextBox.Text?.Length ?? 0; + int newCaret = vm.InsertAutocomplete(item); + if (newCaret >= 0) + { + MessageTextBox.CaretIndex = newCaret; + } _ = MessageTextBox.Focus(); }