Compare commits

...

13 Commits

Author SHA1 Message Date
Mike Griese
b97b69fc6f eh 2025-05-02 16:46:38 -05:00
Mike Griese
39c44aad31 more more cleanup 2025-05-02 10:32:55 -05:00
Mike Griese
90ea33f80c still still works 2025-05-02 10:24:47 -05:00
Mike Griese
707feb49c2 Still works 2025-05-02 10:19:38 -05:00
Mike Griese
c29991f686 At least this works 2025-05-02 10:15:10 -05:00
Mike Griese
b4a20676c6 Revert "the heck why doesn't this work"
This reverts commit 845e4d4877.
2025-05-02 10:09:16 -05:00
Mike Griese
845e4d4877 the heck why doesn't this work 2025-05-02 10:09:11 -05:00
Mike Griese
61665bde49 some small cleanup 2025-05-02 09:03:20 -05:00
Mike Griese
490ae13b50 Load the commands in parallel too
this shaves another 10% off, for a total 70%.

That's 8s -> 3s on average, and now I also get 2.5s reloads. That's
great!
2025-05-02 06:42:13 -05:00
Mike Griese
95d67f4b3e CmdPal: Start extensions in parallel
This reduces our extension startup time by approximately 60% on my
machine (I have 17 extensions). I'd guess the gains scale with the
number of extensions.

This retains the order of the list of extensions, by only starting the
processes in parallel. Once we have all the command provider instances,
then actually retrieving the commands.

RE: #38529
2025-05-02 06:09:32 -05:00
Niels Laute
fe067def65 Fix for missing CmdPal extensions docs (#39173) 2025-05-01 18:33:01 +00:00
Yu Leng
6b9c99c2f6 [cmdpal][AOT] clean up some AOT related issue (#39163)
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
2025-04-30 22:53:06 +08:00
Yu Leng
ba6af794ac [cmdpal] Support search any file in fallback command (#38455)
## Summary of the Pull Request
1. Add new setting to control this behaviour
2. Support always on and only when file exist in the fallback command
3. Change the condition of updateFallbackCommand in toplevelvm

demo:

https://github.com/user-attachments/assets/19e4ced3-30ad-44f4-8f3a-93620f46bb3d


## PR Checklist

- [x] **Closes:** #38370

---------

Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Mike Griese <migrie@microsoft.com>
2025-04-30 06:30:23 -05:00
13 changed files with 369 additions and 129 deletions

View File

@@ -2,8 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
@@ -53,53 +55,44 @@ public partial class TopLevelCommandManager : ObservableObject,
{
CommandProviderWrapper wrapper = new(provider, _taskScheduler);
_builtInCommands.Add(wrapper);
await LoadTopLevelCommandsFromProvider(wrapper);
var commands = await LoadTopLevelCommandsFromProvider(wrapper);
lock (TopLevelCommands)
{
foreach (var c in commands)
{
TopLevelCommands.Add(c);
}
}
}
return true;
}
// May be called from a background thread
private async Task LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
private async Task<IEnumerable<TopLevelViewModel>> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider)
{
WeakReference<IPageContext> weakSelf = new(this);
await commandProvider.LoadTopLevelCommands(_serviceProvider, weakSelf);
var settings = _serviceProvider.GetService<SettingsModel>()!;
var makeAndAdd = (ICommandItem? i, bool fallback) =>
List<TopLevelViewModel> commands = [];
foreach (var item in commandProvider.TopLevelItems)
{
var commandItemViewModel = new CommandItemViewModel(new(i), weakSelf);
var topLevelViewModel = new TopLevelViewModel(commandItemViewModel, fallback, commandProvider.ExtensionHost, commandProvider.ProviderId, settings, _serviceProvider);
commands.Add(item);
}
lock (TopLevelCommands)
{
TopLevelCommands.Add(topLevelViewModel);
}
};
await Task.Factory.StartNew(
() =>
{
lock (TopLevelCommands)
{
foreach (var item in commandProvider.TopLevelItems)
{
TopLevelCommands.Add(item);
}
foreach (var item in commandProvider.FallbackItems)
{
TopLevelCommands.Add(item);
}
}
},
CancellationToken.None,
TaskCreationOptions.None,
_taskScheduler);
foreach (var item in commandProvider.FallbackItems)
{
commands.Add(item);
}
commandProvider.CommandsChanged -= CommandProvider_CommandsChanged;
commandProvider.CommandsChanged += CommandProvider_CommandsChanged;
return commands;
}
// By all accounts, we're already on a background thread (the COM call
@@ -214,7 +207,7 @@ public partial class TopLevelCommandManager : ObservableObject,
_extensionCommandProviders.Clear();
if (extensions != null)
{
await StartExtensionsAndGetCommands(extensions);
StartExtensionsAndGetCommands(extensions);
}
extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded;
@@ -228,36 +221,94 @@ public partial class TopLevelCommandManager : ObservableObject,
private void ExtensionService_OnExtensionAdded(IExtensionService sender, IEnumerable<IExtensionWrapper> extensions)
{
// When we get an extension install event, hop off to a BG thread
_ = Task.Run(async () =>
_ = Task.Run(() =>
{
// for each newly installed extension, start it and get commands
// from it. One single package might have more than one
// IExtensionWrapper in it.
await StartExtensionsAndGetCommands(extensions);
StartExtensionsAndGetCommands(extensions);
});
}
private async Task StartExtensionsAndGetCommands(IEnumerable<IExtensionWrapper> extensions)
private void StartExtensionsAndGetCommands(IEnumerable<IExtensionWrapper> extensions)
{
// TODO This most definitely needs a lock
foreach (var extension in extensions)
{
Logger.LogDebug($"Starting {extension.PackageFullName}");
try
{
// start it ...
await extension.StartExtensionAsync();
var timer = new Stopwatch();
timer.Start();
// ... and fetch the command provider from it.
CommandProviderWrapper wrapper = new(extension, _taskScheduler);
_extensionCommandProviders.Add(wrapper);
await LoadTopLevelCommandsFromProvider(wrapper);
}
catch (Exception ex)
// Start all extensions in parallel using Parallel.ForEach
var wrappers = new ConcurrentBag<CommandProviderWrapper>();
Parallel.ForEach(extensions, extension =>
{
var wrapper = StartExtensionWithTimeoutAsync(extension).GetAwaiter().GetResult();
if (wrapper != null)
{
Logger.LogError(ex.ToString());
wrappers.Add(wrapper);
}
});
foreach (var wrapper in wrappers)
{
_extensionCommandProviders.Add(wrapper);
}
// Load the commands from the providers in parallel
var commandSets = new ConcurrentBag<IEnumerable<TopLevelViewModel>>();
Parallel.ForEach(wrappers, wrapper =>
{
var commands = LoadCommandsWithTimeoutAsync(wrapper).GetAwaiter().GetResult();
if (commands != null)
{
commandSets.Add(commands);
}
});
lock (TopLevelCommands)
{
foreach (var commands in commandSets)
{
foreach (var c in commands)
{
TopLevelCommands.Add(c);
}
}
}
timer.Stop();
Logger.LogDebug($"Loading extensions took {timer.ElapsedMilliseconds} ms");
}
private async Task<CommandProviderWrapper?> StartExtensionWithTimeoutAsync(IExtensionWrapper extension)
{
Logger.LogDebug($"Starting {extension.PackageFullName}");
try
{
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10));
return new CommandProviderWrapper(extension, _taskScheduler);
}
catch (Exception ex)
{
Logger.LogError($"Failed to start extension {extension.PackageFullName}: {ex}");
return null; // Return null for failed extensions
}
}
private async Task<IEnumerable<TopLevelViewModel>?> LoadCommandsWithTimeoutAsync(CommandProviderWrapper wrapper)
{
try
{
return await LoadTopLevelCommandsFromProvider(wrapper!).WaitAsync(TimeSpan.FromSeconds(10));
}
catch (TimeoutException)
{
Logger.LogError($"Loading commands from {wrapper!.ExtensionHost?.Extension?.PackageFullName} timed out");
}
catch (Exception ex)
{
Logger.LogError($"Failed to load commands for extension {wrapper!.ExtensionHost?.Extension?.PackageFullName}: {ex}");
}
return null;
}
private void ExtensionService_OnExtensionRemoved(IExtensionService sender, IEnumerable<IExtensionWrapper> extensions)

View File

@@ -9,7 +9,7 @@ By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.
The fastest way to get started is just to run the "Create extension" command in the palette itself. That'll prompt you for a project name and a Display Name, and where you want to place your project. Then just open the `sln` it produces. You should be ready to go 🙂.
The official API documentation can be found [on this docs site](TODO! Add docs link when we have one)
The official API documentation can be found [on this docs site](https://learn.microsoft.com/windows/powertoys/command-palette/extensibility-overview).
We've also got samples, so that you can see how the APIs in-action.

View File

@@ -12,7 +12,7 @@ using Microsoft.CmdPal.Ext.Apps.Utils;
namespace Microsoft.CmdPal.Ext.Apps;
public sealed class AppCache : IDisposable
public sealed partial class AppCache : IDisposable
{
private Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;

View File

@@ -319,7 +319,7 @@ public static class ReparsePoint
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
{
var dataOffset = Marshal.SizeOf(typeof(AppExecutionAliasReparseTagHeader));
var dataOffset = Marshal.SizeOf<AppExecutionAliasReparseTagHeader>();
var dataBufferPtr = reparseDataBufferPtr + dataOffset;
string? packageFullName = null;

View File

@@ -8,7 +8,7 @@ using System.IO;
namespace Microsoft.CmdPal.Ext.Apps.Storage;
// File System Watcher Wrapper class which implements the IFileSystemWatcherWrapper interface
public sealed class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
public sealed partial class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
{
public FileSystemWatcherWrapper()
{

View File

@@ -15,7 +15,7 @@ namespace Microsoft.CmdPal.Ext.Apps.Storage;
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
/// </summary>
internal sealed class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
internal sealed partial class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
{
private readonly IPackageCatalog _packageCatalog;

View File

@@ -9,7 +9,7 @@ using System.Linq;
namespace Microsoft.CmdPal.Ext.Apps.Storage;
internal sealed class Win32ProgramFileSystemWatchers : IDisposable
internal sealed partial class Win32ProgramFileSystemWatchers : IDisposable
{
public string[] PathsToWatch { get; set; }

View File

@@ -14,7 +14,7 @@ using Win32Program = Microsoft.CmdPal.Ext.Apps.Programs.Win32Program;
namespace Microsoft.CmdPal.Ext.Apps.Storage;
internal sealed class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
internal sealed partial class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;

View File

@@ -2,7 +2,10 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Text;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
@@ -10,8 +13,14 @@ using Windows.Storage.Streams;
namespace Microsoft.CmdPal.Ext.Indexer;
internal sealed partial class FallbackOpenFileItem : FallbackCommandItem
internal sealed partial class FallbackOpenFileItem : FallbackCommandItem, System.IDisposable
{
private readonly CompositeFormat fallbackItemSearchPageTitleCompositeFormat = CompositeFormat.Parse(Resources.Indexer_fallback_searchPage_title);
private readonly SearchEngine _searchEngine = new();
private uint _queryCookie = 10;
public FallbackOpenFileItem()
: base(new NoOpCommand(), Resources.Indexer_Find_Path_fallback_display_title)
{
@@ -21,8 +30,20 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem
public override void UpdateQuery(string query)
{
if (string.IsNullOrWhiteSpace(query))
{
Command = new NoOpCommand();
Title = string.Empty;
Subtitle = string.Empty;
Icon = null;
MoreCommands = null;
return;
}
if (Path.Exists(query))
{
// Exit 1: The query is a direct path to a file. Great! Return it.
var item = new IndexerItem() { FullPath = query, FileName = Path.GetFileName(query) };
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.AsDefault);
Command = listItemForUs.Command;
@@ -43,12 +64,65 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem
catch
{
}
return;
}
else
{
Title = string.Empty;
Subtitle = string.Empty;
Command = new NoOpCommand();
_queryCookie++;
try
{
_searchEngine.Query(query, _queryCookie);
var results = _searchEngine.FetchItems(0, 20, _queryCookie, out var _);
if (results.Count == 0 || ((results[0] as IndexerListItem) == null))
{
// Exit 2: We searched for the file, and found nothing. Oh well.
// Hide ourselves.
Title = string.Empty;
Subtitle = string.Empty;
Command = new NoOpCommand();
return;
}
if (results.Count == 1)
{
// Exit 3: We searched for the file, and found exactly one thing. Awesome!
// Return it.
Title = results[0].Title;
Subtitle = results[0].Subtitle;
Icon = results[0].Icon;
Command = results[0].Command;
MoreCommands = results[0].MoreCommands;
return;
}
// Exit 4: We found more than one result. Make our command take
// us to the file search page, prepopulated with this search.
var indexerPage = new IndexerPage(query, _searchEngine, _queryCookie, results);
Title = string.Format(CultureInfo.CurrentCulture, fallbackItemSearchPageTitleCompositeFormat, query);
Icon = Icons.FileExplorer;
Subtitle = Resources.Indexer_Subtitle;
Command = indexerPage;
return;
}
catch
{
Title = string.Empty;
Subtitle = string.Empty;
Icon = null;
Command = new NoOpCommand();
MoreCommands = null;
}
}
}
public void Dispose()
{
_searchEngine.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -4,25 +4,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Indexer;
using Microsoft.CmdPal.Ext.Indexer.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
namespace Microsoft.CmdPal.Ext.Indexer;
internal sealed partial class IndexerPage : DynamicListPage, IDisposable
{
private readonly List<IListItem> _indexerListItems = [];
private readonly SearchEngine _searchEngine;
private readonly bool disposeSearchEngine = true;
private SearchQuery _searchQuery = new();
private uint _queryCookie;
private uint _queryCookie = 10;
private string initialQuery = string.Empty;
public IndexerPage()
{
@@ -30,16 +27,31 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
Icon = Icons.FileExplorer;
Name = Resources.Indexer_Title;
PlaceholderText = Resources.Indexer_PlaceholderText;
_searchEngine = new();
_queryCookie = 10;
}
public IndexerPage(string query, SearchEngine searchEngine, uint queryCookie, IList<IListItem> firstPageData)
{
Icon = Icons.FileExplorer;
Name = Resources.Indexer_Title;
_searchEngine = searchEngine;
_queryCookie = queryCookie;
_indexerListItems.AddRange(firstPageData);
initialQuery = query;
SearchText = query;
disposeSearchEngine = false;
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
if (oldSearch != newSearch)
if (oldSearch != newSearch && newSearch != initialQuery)
{
_ = Task.Run(() =>
{
Query(newSearch);
LoadMore();
initialQuery = string.Empty;
});
}
}
@@ -49,7 +61,9 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
public override void LoadMore()
{
IsLoading = true;
FetchItems(20);
var results = _searchEngine.FetchItems(_indexerListItems.Count, 20, _queryCookie, out var hasMore);
_indexerListItems.AddRange(results);
HasMoreItems = hasMore;
IsLoading = false;
RaiseItemsChanged(_indexerListItems.Count);
}
@@ -58,70 +72,16 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
{
++_queryCookie;
_indexerListItems.Clear();
_searchQuery.SearchResults.Clear();
_searchQuery.CancelOutstandingQueries();
if (query == string.Empty)
{
return;
}
Stopwatch stopwatch = new();
stopwatch.Start();
_searchQuery.Execute(query, _queryCookie);
stopwatch.Stop();
Logger.LogDebug($"Query time: {stopwatch.ElapsedMilliseconds} ms, query: \"{query}\"");
}
private void FetchItems(int limit)
{
if (_searchQuery != null)
{
var cookie = _searchQuery.Cookie;
if (cookie == _queryCookie)
{
var index = 0;
SearchResult result;
var hasMoreItems = _searchQuery.FetchRows(_indexerListItems.Count, limit);
while (!_searchQuery.SearchResults.IsEmpty && _searchQuery.SearchResults.TryDequeue(out result) && ++index <= limit)
{
IconInfo icon = null;
try
{
var stream = ThumbnailHelper.GetThumbnail(result.LaunchUri).Result;
if (stream != null)
{
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
icon = new IconInfo(data, data);
}
}
catch (Exception ex)
{
Logger.LogError("Failed to get the icon.", ex);
}
_indexerListItems.Add(new IndexerListItem(new IndexerItem
{
FileName = result.ItemDisplayName,
FullPath = result.LaunchUri,
})
{
Icon = icon,
});
}
HasMoreItems = hasMoreItems;
}
}
_searchEngine.Query(query, _queryCookie);
}
public void Dispose()
{
_searchQuery = null;
GC.SuppressFinalize(this);
if (disposeSearchEngine)
{
_searchEngine.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@@ -123,6 +123,15 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Search for &quot;{0}&quot; in files.
/// </summary>
internal static string Indexer_fallback_searchPage_title {
get {
return ResourceManager.GetString("Indexer_fallback_searchPage_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This file doesn&apos;t exist.
/// </summary>
@@ -168,6 +177,42 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Always on.
/// </summary>
internal static string Indexer_Settings_FallbackCommand_AlwaysOn {
get {
return ResourceManager.GetString("Indexer_Settings_FallbackCommand_AlwaysOn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Only when file path exist.
/// </summary>
internal static string Indexer_Settings_FallbackCommand_FilePathExist {
get {
return ResourceManager.GetString("Indexer_Settings_FallbackCommand_FilePathExist", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Shows file search results on the top-level search results.
/// </summary>
internal static string Indexer_Settings_FallbackCommand_Mode {
get {
return ResourceManager.GetString("Indexer_Settings_FallbackCommand_Mode", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Always off.
/// </summary>
internal static string Indexer_Settings_FallbackCommand_Off {
get {
return ResourceManager.GetString("Indexer_Settings_FallbackCommand_Off", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search files on this device.
/// </summary>

View File

@@ -156,10 +156,25 @@
<data name="Indexer_PlaceholderText" xml:space="preserve">
<value>Search for files and folders...</value>
</data>
<data name="Indexer_Settings_FallbackCommand_AlwaysOn" xml:space="preserve">
<value>Always on</value>
</data>
<data name="Indexer_Settings_FallbackCommand_Mode" xml:space="preserve">
<value>Shows file search results on the top-level search results</value>
</data>
<data name="Indexer_Settings_FallbackCommand_Off" xml:space="preserve">
<value>Always off</value>
</data>
<data name="Indexer_Settings_FallbackCommand_FilePathExist" xml:space="preserve">
<value>Only when file path exist</value>
</data>
<data name="Indexer_Subtitle" xml:space="preserve">
<value>Search files on this device</value>
</data>
<data name="Indexer_Title" xml:space="preserve">
<value>Search files</value>
</data>
<data name="Indexer_fallback_searchPage_title" xml:space="preserve">
<value>Search for "{0}" in files</value>
</data>
</root>

View File

@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ManagedCommon;
using Microsoft.CmdPal.Ext.Indexer.Data;
using Microsoft.CmdPal.Ext.Indexer.Indexer;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.Storage.Streams;
namespace Microsoft.CmdPal.Ext.Indexer;
public sealed partial class SearchEngine : IDisposable
{
private SearchQuery _searchQuery = new();
public void Query(string query, uint queryCookie)
{
// _indexerListItems.Clear();
_searchQuery.SearchResults.Clear();
_searchQuery.CancelOutstandingQueries();
if (query == string.Empty)
{
return;
}
Stopwatch stopwatch = new();
stopwatch.Start();
_searchQuery.Execute(query, queryCookie);
stopwatch.Stop();
Logger.LogDebug($"Query time: {stopwatch.ElapsedMilliseconds} ms, query: \"{query}\"");
}
public IList<IListItem> FetchItems(int offset, int limit, uint queryCookie, out bool hasMore)
{
hasMore = false;
var results = new List<IListItem>();
if (_searchQuery != null)
{
var cookie = _searchQuery.Cookie;
if (cookie == queryCookie)
{
var index = 0;
SearchResult result;
// var hasMoreItems = _searchQuery.FetchRows(_indexerListItems.Count, limit);
var hasMoreItems = _searchQuery.FetchRows(offset, limit);
while (!_searchQuery.SearchResults.IsEmpty && _searchQuery.SearchResults.TryDequeue(out result) && ++index <= limit)
{
IconInfo icon = null;
try
{
var stream = ThumbnailHelper.GetThumbnail(result.LaunchUri).Result;
if (stream != null)
{
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
icon = new IconInfo(data, data);
}
}
catch (Exception ex)
{
Logger.LogError("Failed to get the icon.", ex);
}
results.Add(new IndexerListItem(new IndexerItem
{
FileName = result.ItemDisplayName,
FullPath = result.LaunchUri,
})
{
Icon = icon,
});
}
hasMore = hasMoreItems;
}
}
return results;
}
public void Dispose()
{
_searchQuery = null;
GC.SuppressFinalize(this);
}
}