mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-01 09:56:32 +01:00
Compare commits
13 Commits
feature/Ke
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b97b69fc6f | ||
|
|
39c44aad31 | ||
|
|
90ea33f80c | ||
|
|
707feb49c2 | ||
|
|
c29991f686 | ||
|
|
b4a20676c6 | ||
|
|
845e4d4877 | ||
|
|
61665bde49 | ||
|
|
490ae13b50 | ||
|
|
95d67f4b3e | ||
|
|
fe067def65 | ||
|
|
6b9c99c2f6 | ||
|
|
ba6af794ac |
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,15 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for "{0}" 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'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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user