diff --git a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs index c3065f4780de..5c2ce55253f0 100644 --- a/src/Files.App.Controls/Omnibar/Omnibar.Events.cs +++ b/src/Files.App.Controls/Omnibar/Omnibar.Events.cs @@ -134,13 +134,13 @@ private void AutoSuggestBox_TextChanged(object sender, TextChangedEventArgs e) if (string.Compare(_textBox.Text, CurrentSelectedMode!.Text, StringComparison.OrdinalIgnoreCase) is not 0) CurrentSelectedMode!.Text = _textBox.Text; - // UpdateSuggestionListView(); - if (_textChangeReason is OmnibarTextChangeReason.ProgrammaticChange) _textBox.SelectAll(); else { _userInput = _textBox.Text; + if (_userInput.Length == 0) + _textChangeReason = OmnibarTextChangeReason.UserInput; } TextChanged?.Invoke(this, new(CurrentSelectedMode, _textChangeReason)); diff --git a/src/Files.App/Data/Items/NavigationBarSuggestionItem.cs b/src/Files.App/Data/Items/NavigationBarSuggestionItem.cs index f5eb7895a255..ef5096114b6b 100644 --- a/src/Files.App/Data/Items/NavigationBarSuggestionItem.cs +++ b/src/Files.App/Data/Items/NavigationBarSuggestionItem.cs @@ -3,97 +3,60 @@ using Files.App.Controls; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Media; namespace Files.App.Data.Items { [Obsolete("Remove once Omnibar goes out of experimental.")] - public sealed partial class NavigationBarSuggestionItem : ObservableObject, IOmnibarTextMemberPathProvider + public sealed partial class NavigationBarSuggestionItem : IOmnibarTextMemberPathProvider { - private ImageSource? _ActionIconSource; - public ImageSource? ActionIconSource { get => _ActionIconSource; set => SetProperty(ref _ActionIconSource, value); } + public IRichCommand? Command { get; } - private Style? _ThemedIconStyle; - public Style? ThemedIconStyle { get => _ThemedIconStyle; set => SetProperty(ref _ThemedIconStyle, value); } + public Style? ThemedIconStyle { get; } - private string? _Glyph; - public string? Glyph { get => _Glyph; set => SetProperty(ref _Glyph, value); } + public string? Glyph { get; } - private string? _Text; - public string? Text { get => _Text; set => SetProperty(ref _Text, value); } + public string Text { get; } - private string? _PrimaryDisplay; - public string? PrimaryDisplay - { - get => _PrimaryDisplay; - set - { - if (SetProperty(ref _PrimaryDisplay, value)) - UpdatePrimaryDisplay(); - } - } + public string? PrimaryDisplayPreMatched { get; } - private string? _SearchText; - public string? SearchText - { - get => _SearchText; - set - { - if (SetProperty(ref _SearchText, value)) - UpdatePrimaryDisplay(); - } - } + public string? PrimaryDisplayMatched { get; } - private string? _PrimaryDisplayPreMatched; - public string? PrimaryDisplayPreMatched - { - get => _PrimaryDisplayPreMatched; - private set => SetProperty(ref _PrimaryDisplayPreMatched, value); - } + public string? PrimaryDisplayPostMatched { get; } - private string? _PrimaryDisplayMatched; - public string? PrimaryDisplayMatched - { - get => _PrimaryDisplayMatched; - private set => SetProperty(ref _PrimaryDisplayMatched, value); - } + public HotKeyCollection HotKeys { get; } - private string? _PrimaryDisplayPostMatched; - public string? PrimaryDisplayPostMatched + public NavigationBarSuggestionItem(string? searchText, IRichCommand? command) { - get => _PrimaryDisplayPostMatched; - private set => SetProperty(ref _PrimaryDisplayPostMatched, value); - } + Command = command; - private HotKeyCollection _HotKeys = new(); - public HotKeyCollection HotKeys - { - get => _HotKeys; - set => SetProperty(ref _HotKeys, value); - } + if (command is null) + { + Text = string.Format(Strings.NoCommandsFound.GetLocalizedResource(), searchText); + } + else + { + ThemedIconStyle = command.ThemedIconStyle; + Glyph = command.Glyph.BaseGlyph; + Text = command.Description; + HotKeys = command.HotKeys; + } - private void UpdatePrimaryDisplay() - { - if (SearchText is null || PrimaryDisplay is null) + if (searchText is null) { - PrimaryDisplayPreMatched = null; - PrimaryDisplayMatched = PrimaryDisplay; - PrimaryDisplayPostMatched = null; + PrimaryDisplayMatched = Text; } else { - var index = PrimaryDisplay.IndexOf(SearchText, StringComparison.OrdinalIgnoreCase); + var index = Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase); if (index < 0) { - PrimaryDisplayPreMatched = PrimaryDisplay; - PrimaryDisplayMatched = null; - PrimaryDisplayPostMatched = null; + PrimaryDisplayPreMatched = Text; } else { - PrimaryDisplayPreMatched = PrimaryDisplay.Substring(0, index); - PrimaryDisplayMatched = PrimaryDisplay.Substring(index, SearchText.Length); - PrimaryDisplayPostMatched = PrimaryDisplay.Substring(index + SearchText.Length); + PrimaryDisplayPreMatched = Text.Substring(0, index); + PrimaryDisplayMatched = Text.Substring(index, searchText.Length); + PrimaryDisplayPostMatched = Text.Substring(index + searchText.Length); } } } @@ -103,8 +66,6 @@ public string GetTextMemberPath(string textMemberPath) return textMemberPath switch { nameof(Text) => Text, - nameof(PrimaryDisplay) => PrimaryDisplay, - nameof(SearchText) => SearchText, _ => string.Empty }; } diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml b/src/Files.App/UserControls/NavigationToolbar.xaml index 3ec89274a576..fa070de7be8d 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml +++ b/src/Files.App/UserControls/NavigationToolbar.xaml @@ -309,11 +309,10 @@ - + Visibility="{x:Bind Glyph, Converter={StaticResource NullToVisibilityCollapsedConverter}, Mode=OneTime}"> + - - + @@ -324,7 +323,7 @@ Foreground="{ThemeResource TextFillColorPrimaryBrush}" TextTrimming="CharacterEllipsis" TextWrapping="NoWrap"> - + @@ -332,7 +331,7 @@ x:Name="RightAlignedKeyboardShortcut" Grid.Column="2" VerticalAlignment="Center" - HotKeys="{x:Bind HotKeys, Mode=OneWay}" /> + HotKeys="{x:Bind HotKeys, Mode=OneTime}" /> diff --git a/src/Files.App/UserControls/NavigationToolbar.xaml.cs b/src/Files.App/UserControls/NavigationToolbar.xaml.cs index 0311b57e320a..fc8ec5ad6da5 100644 --- a/src/Files.App/UserControls/NavigationToolbar.xaml.cs +++ b/src/Files.App/UserControls/NavigationToolbar.xaml.cs @@ -183,25 +183,24 @@ private async void Omnibar_QuerySubmitted(Omnibar sender, OmnibarQuerySubmittedE // Command palette mode else if (mode == OmnibarCommandPaletteMode) { - var item = args.Item as NavigationBarSuggestionItem; + IRichCommand? command = null; - // Try invoking built-in command - foreach (var command in Commands) - { - if (command == Commands.None) - continue; - - if (!string.Equals(command.Description, item?.Text, StringComparison.OrdinalIgnoreCase) && - !string.Equals(command.Description, args.Text, StringComparison.OrdinalIgnoreCase)) - continue; + if (args.Item is NavigationBarSuggestionItem item) + command = item.Command; + else + // Maybe the user typed the full command description without selecting a suggestion, so search for it + foreach (var c in Commands) + if (c != Commands.None && string.Equals(c.Description, args.Text, StringComparison.OrdinalIgnoreCase)) + { + command = c; + break; + } + if (command is not null) await command.ExecuteAsync(); - ContentPageContext.ShellPage!.PaneHolder.FocusActivePane(); - return; - } - - await DialogDisplayHelper.ShowDialogAsync(Strings.InvalidCommand.GetLocalizedResource(), - string.Format(Strings.InvalidCommandContent.GetLocalizedResource(), args.Text)); + else + await DialogDisplayHelper.ShowDialogAsync(Strings.InvalidCommand.GetLocalizedResource(), + string.Format(Strings.InvalidCommandContent.GetLocalizedResource(), args.Text)); ContentPageContext.ShellPage!.PaneHolder.FocusActivePane(); return; diff --git a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs index 901f23a0f0c1..df3372307651 100644 --- a/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs @@ -5,7 +5,6 @@ using Files.App.Controls; using Files.App.ViewModels.Settings; using Files.Shared.Helpers; -using Microsoft.Extensions.Logging; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -1030,12 +1029,12 @@ public async Task PopulateOmnibarSuggestionsForPathMode() void AddNoResultsItem() { PathModeSuggestionItems.Clear(); - + // Use null-safe access to avoid NullReferenceException during app lifecycle transitions var workingDirectory = string.IsNullOrEmpty(ContentPageContext.ShellPage?.ShellViewModel?.WorkingDirectory) ? Constants.UserEnvironmentPaths.HomePath : ContentPageContext.ShellPage.ShellViewModel.WorkingDirectory; - + PathModeSuggestionItems.Add(new( workingDirectory, Strings.NavigationToolbarVisiblePathNoResults.GetLocalizedResource())); @@ -1044,97 +1043,54 @@ void AddNoResultsItem() public async Task PopulateOmnibarSuggestionsForCommandPaletteMode() { - var (suggestionsToProcess, commandsToProcess) = await Task.Run(() => - { - var suggestions = new List(); + string? omnibarCommandPaletteModeText = OmnibarCommandPaletteModeText; - var commandsData = Commands - .Where(command => command.IsAccessibleGlobally - && (command.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase) - || command.Code.ToString().Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase))) - .Where(command => command.Description != Commands.OpenCommandPalette.Description.ToString()) - .ToList(); - - return (suggestions, commandsData); + var commandsToProcess = await Task.Run(() => + { + if (string.IsNullOrEmpty(OmnibarCommandPaletteModeText)) + return Commands.Where(static command => command.IsAccessibleGlobally && command.Code != CommandCodes.OpenCommandPalette).ToList(); + else + return Commands + .Where(command => command.IsAccessibleGlobally + && (command.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase) + || command.Code.ToString().Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)) + && command.Code != CommandCodes.OpenCommandPalette) + .ToList(); }); - var newSuggestions = new List(suggestionsToProcess); - int processedCount = 0; + if (OmnibarCommandPaletteModeText != omnibarCommandPaletteModeText) + // We're still processing old input, user has typed in the mean time. + return; - foreach (var command in commandsToProcess) - { - if (!command.IsExecutable) - { - processedCount++; - // To allow UI updates - if (processedCount % 3 == 0) - await Task.Yield(); - continue; - } + int count = 0; - var newItem = new NavigationBarSuggestionItem + foreach (var command in commandsToProcess) + { + if (command.IsExecutable) { - ThemedIconStyle = command.Glyph.ToThemedIconStyle(), - Glyph = command.Glyph.BaseGlyph, - Text = command.Description, - PrimaryDisplay = command.Description, - HotKeys = command.HotKeys, - SearchText = OmnibarCommandPaletteModeText, - }; + var newSuggestion = new NavigationBarSuggestionItem(OmnibarCommandPaletteModeText, command); - newSuggestions.Add(newItem); - processedCount++; + if (count < OmnibarCommandPaletteModeSuggestionItems.Count) + OmnibarCommandPaletteModeSuggestionItems[count] = newSuggestion; + else + OmnibarCommandPaletteModeSuggestionItems.Add(newSuggestion); - // To allow UI updates - if (processedCount % 3 == 0) - await Task.Yield(); - } + count++; - UpdateCommandPaletteSuggestions(newSuggestions); - } - - private void UpdateCommandPaletteSuggestions(List newSuggestions) - { - if (newSuggestions.Count == 0) - { - newSuggestions.Add(new NavigationBarSuggestionItem() - { - PrimaryDisplay = string.Format(Strings.NoCommandsFound.GetLocalizedResource(), OmnibarCommandPaletteModeText), - SearchText = OmnibarCommandPaletteModeText, - }); + // To allow UI updates + if (count % 4 == 0) + await Task.Yield(); + } } - if (!OmnibarCommandPaletteModeSuggestionItems.IntersectBy(newSuggestions, x => x.PrimaryDisplay).Any()) + if (count == 0) { - for (int index = 0; index < newSuggestions.Count; index++) - { - if (index < OmnibarCommandPaletteModeSuggestionItems.Count) - OmnibarCommandPaletteModeSuggestionItems[index] = newSuggestions[index]; - else - OmnibarCommandPaletteModeSuggestionItems.Add(newSuggestions[index]); - } - - while (OmnibarCommandPaletteModeSuggestionItems.Count > newSuggestions.Count) - OmnibarCommandPaletteModeSuggestionItems.RemoveAt(OmnibarCommandPaletteModeSuggestionItems.Count - 1); + OmnibarCommandPaletteModeSuggestionItems.Clear(); + OmnibarCommandPaletteModeSuggestionItems.Add(new NavigationBarSuggestionItem(OmnibarCommandPaletteModeText, null)); } else - { - foreach (var s in OmnibarCommandPaletteModeSuggestionItems.ExceptBy(newSuggestions, x => x.PrimaryDisplay).ToList()) - OmnibarCommandPaletteModeSuggestionItems.Remove(s); - - for (int index = 0; index < newSuggestions.Count; index++) - { - if (OmnibarCommandPaletteModeSuggestionItems.Count > index - && OmnibarCommandPaletteModeSuggestionItems[index].PrimaryDisplay == newSuggestions[index].PrimaryDisplay) - { - OmnibarCommandPaletteModeSuggestionItems[index] = newSuggestions[index]; - } - else - { - OmnibarCommandPaletteModeSuggestionItems.Insert(index, newSuggestions[index]); - } - } - } + while (OmnibarCommandPaletteModeSuggestionItems.Count > count) + OmnibarCommandPaletteModeSuggestionItems.RemoveAt(OmnibarCommandPaletteModeSuggestionItems.Count - 1); } public async Task PopulateOmnibarSuggestionsForSearchMode()