Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Files.App.Controls/Omnibar/Omnibar.Events.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
97 changes: 29 additions & 68 deletions src/Files.App/Data/Items/NavigationBarSuggestionItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand All @@ -103,8 +66,6 @@ public string GetTextMemberPath(string textMemberPath)
return textMemberPath switch
{
nameof(Text) => Text,
nameof(PrimaryDisplay) => PrimaryDisplay,
nameof(SearchText) => SearchText,
_ => string.Empty
};
}
Expand Down
11 changes: 5 additions & 6 deletions src/Files.App/UserControls/NavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,10 @@
<Viewbox
Width="16"
Height="16"
Visibility="{x:Bind Glyph, Converter={StaticResource NullToVisibilityCollapsedConverter}, Mode=OneWay}">
<FontIcon Foreground="{ThemeResource App.Theme.IconBaseBrush}" Glyph="{x:Bind Glyph, Mode=OneWay}" />
Visibility="{x:Bind Glyph, Converter={StaticResource NullToVisibilityCollapsedConverter}, Mode=OneTime}">
<FontIcon Foreground="{ThemeResource App.Theme.IconBaseBrush}" Glyph="{x:Bind Glyph, Mode=OneTime}" />
</Viewbox>
<controls:ThemedIcon Style="{x:Bind ThemedIconStyle, Mode=OneWay}" Visibility="{x:Bind ThemedIconStyle, Converter={StaticResource NullToVisibilityCollapsedConverter}, Mode=OneWay}" />
<Image Source="{x:Bind ActionIconSource, Mode=OneWay}" />
<controls:ThemedIcon Style="{x:Bind ThemedIconStyle, Mode=OneTime}" Visibility="{x:Bind ThemedIconStyle, Converter={StaticResource NullToVisibilityCollapsedConverter}, Mode=OneTime}" />
</Grid>

<!-- Primary Title -->
Expand All @@ -324,15 +323,15 @@
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap">
<Run FontWeight="Normal" Text="{x:Bind PrimaryDisplayPreMatched, Mode=OneWay}" /><Run FontWeight="Bold" Text="{x:Bind PrimaryDisplayMatched, Mode=OneWay}" /><Run FontWeight="Normal" Text="{x:Bind PrimaryDisplayPostMatched, Mode=OneWay}" />
<Run FontWeight="Normal" Text="{x:Bind PrimaryDisplayPreMatched, Mode=OneTime}" /><Run FontWeight="Bold" Text="{x:Bind PrimaryDisplayMatched, Mode=OneTime}" /><Run FontWeight="Normal" Text="{x:Bind PrimaryDisplayPostMatched, Mode=OneTime}" />
</TextBlock>

<!-- Keyboard Shortcuts -->
<keyboard:KeyboardShortcut
x:Name="RightAlignedKeyboardShortcut"
Grid.Column="2"
VerticalAlignment="Center"
HotKeys="{x:Bind HotKeys, Mode=OneWay}" />
HotKeys="{x:Bind HotKeys, Mode=OneTime}" />
</Grid>
</DataTemplate>
</controls:OmnibarMode.ItemTemplate>
Expand Down
31 changes: 15 additions & 16 deletions src/Files.App/UserControls/NavigationToolbar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
116 changes: 36 additions & 80 deletions src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
Expand All @@ -1044,97 +1043,54 @@ void AddNoResultsItem()

public async Task PopulateOmnibarSuggestionsForCommandPaletteMode()
{
var (suggestionsToProcess, commandsToProcess) = await Task.Run(() =>
{
var suggestions = new List<NavigationBarSuggestionItem>();
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<NavigationBarSuggestionItem>(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<NavigationBarSuggestionItem> 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()
Expand Down
Loading