diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs index c47e66b76e..4fd4e1ea27 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/MainListPage.cs @@ -10,6 +10,8 @@ using ManagedCommon; using Microsoft.CmdPal.Core.Common.Helpers; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Ext.Apps; +using Microsoft.CmdPal.Ext.Apps.Programs; +using Microsoft.CmdPal.Ext.Apps.State; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Properties; using Microsoft.CommandPalette.Extensions; @@ -36,6 +38,7 @@ public partial class MainListPage : DynamicListPage, private List>? _filteredItems; private List>? _filteredApps; private List>? _fallbackItems; + private IEnumerable>? _scoredFallbackItems; private bool _includeApps; private bool _filteredItemsIncludesApps; private int _appResultLimit = 10; @@ -171,6 +174,7 @@ public partial class MainListPage : DynamicListPage, var items = Enumerable.Empty>() .Concat(_filteredItems is not null ? _filteredItems : []) + .Concat(_scoredFallbackItems is not null ? _scoredFallbackItems : []) .Concat(limitedApps) .OrderByDescending(o => o.Score) @@ -184,6 +188,14 @@ public partial class MainListPage : DynamicListPage, } } + private void ClearResults() + { + _filteredItems = null; + _filteredApps = null; + _fallbackItems = null; + _scoredFallbackItems = null; + } + public override void UpdateSearchText(string oldSearch, string newSearch) { var timer = new Stopwatch(); @@ -216,8 +228,7 @@ public partial class MainListPage : DynamicListPage, lock (_tlcManager.TopLevelCommands) { _filteredItemsIncludesApps = _includeApps; - _filteredItems = null; - _filteredApps = null; + ClearResults(); } } @@ -244,9 +255,7 @@ public partial class MainListPage : DynamicListPage, if (string.IsNullOrEmpty(newSearch)) { _filteredItemsIncludesApps = _includeApps; - _filteredItems = null; - _filteredApps = null; - _fallbackItems = null; + ClearResults(); RaiseItemsChanged(commands.Count); return; } @@ -255,17 +264,13 @@ public partial class MainListPage : DynamicListPage, // re-use previous results. Reset _filteredItems, and keep er moving. if (!newSearch.StartsWith(oldSearch, StringComparison.CurrentCultureIgnoreCase)) { - _filteredItems = null; - _filteredApps = null; - _fallbackItems = null; + ClearResults(); } // If the internal state has changed, reset _filteredItems to reset the list. if (_filteredItemsIncludesApps != _includeApps) { - _filteredItems = null; - _filteredApps = null; - _fallbackItems = null; + ClearResults(); } if (token.IsCancellationRequested) @@ -314,7 +319,7 @@ public partial class MainListPage : DynamicListPage, // We're going to start over with our fallbacks newFallbacks = Enumerable.Empty(); - newFilteredItems = commands.Where(s => !s.IsFallback || _specialFallbacks.Contains(s.CommandProviderId)); + newFilteredItems = commands.Where(s => !s.IsFallback); // Fallbacks are always included in the list, even if they // don't match the search text. But we don't want to @@ -330,7 +335,20 @@ public partial class MainListPage : DynamicListPage, if (_includeApps) { - newApps = AllAppsCommandProvider.Page.GetItems().ToList(); + var allNewApps = AllAppsCommandProvider.Page.GetItems().ToList(); + + // We need to remove pinned apps from allNewApps so they don't show twice. + var pinnedApps = PinnedAppsManager.Instance.GetPinnedAppIdentifiers(); + + if (pinnedApps.Length > 0) + { + newApps = allNewApps.Where(w => + pinnedApps.IndexOf(((AppListItem)w).AppIdentifier) < 0); + } + else + { + newApps = allNewApps; + } } if (token.IsCancellationRequested) @@ -350,6 +368,20 @@ public partial class MainListPage : DynamicListPage, return; } + IEnumerable newFallbacksForScoring = commands.Where(s => s.IsFallback && _specialFallbacks.Contains(s.CommandProviderId)); + + if (token.IsCancellationRequested) + { + return; + } + + _scoredFallbackItems = ListHelpers.FilterListWithScores(newFallbacksForScoring ?? [], SearchText, scoreItem); + + if (token.IsCancellationRequested) + { + return; + } + // Defaulting scored to 1 but we'll eventually use user rankings _fallbackItems = [.. newFallbacks.Select(f => new Scored { Item = f, Score = 1 })]; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs index 34f6c9c9c5..d97bde7037 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Apps/AppListItem.cs @@ -13,7 +13,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CmdPal.Ext.Apps.Programs; -internal sealed partial class AppListItem : ListItem +public sealed partial class AppListItem : ListItem { private static readonly Tag _appTag = new("App"); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs index 2e4cac7b16..ab557ba258 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs @@ -16,7 +16,6 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos private readonly Action? _addToHistory; private readonly ITelemetryService _telemetryService; private CancellationTokenSource? _cancellationTokenSource; - private Task? _currentUpdateTask; public FallbackExecuteItem(SettingsManager settings, Action? addToHistory, ITelemetryService telemetryService) : base( @@ -40,44 +39,22 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos try { - // Save the latest update task - _currentUpdateTask = DoUpdateQueryAsync(query, cancellationToken); - } - catch (OperationCanceledException) - { - // DO NOTHING HERE - return; + DoUpdateQuery(query, cancellationToken); } catch (Exception) { // Handle other exceptions return; } - - // Await the task to ensure only the latest one gets processed - _ = ProcessUpdateResultsAsync(_currentUpdateTask); } - private async Task ProcessUpdateResultsAsync(Task updateTask) - { - try - { - await updateTask; - } - catch (OperationCanceledException) - { - // Handle cancellation gracefully - } - catch (Exception) - { - // Handle other exceptions - } - } - - private async Task DoUpdateQueryAsync(string query, CancellationToken cancellationToken) + private void DoUpdateQuery(string query, CancellationToken cancellationToken) { // Check for cancellation at the start - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return; + } var searchText = query.Trim(); Expand(ref searchText); @@ -105,22 +82,8 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token); var timeoutToken = combinedCts.Token; - // Use Task.Run with timeout for file system operations - var fileSystemTask = Task.Run( - () => - { - exeExists = ShellListPageHelpers.FileExistInPath(exe, out fullExePath); - pathIsDir = Directory.Exists(exe); - }, - CancellationToken.None); - - // Wait for either completion or timeout - await fileSystemTask.WaitAsync(timeoutToken); - } - catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) - { - // Main cancellation token was cancelled, re-throw - throw; + exeExists = ShellListPageHelpers.FileExistInPath(exe, out fullExePath, cancellationToken); + pathIsDir = Directory.Exists(exe); } catch (TimeoutException) { @@ -139,7 +102,10 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos } // Check for cancellation before updating UI properties - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return; + } if (exeExists) { @@ -172,7 +138,10 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos } // Final cancellation check - cancellationToken.ThrowIfCancellationRequested(); + if (cancellationToken.IsCancellationRequested) + { + return; + } } public void Dispose()