mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-07 19:57:07 +02:00
[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>
This commit is contained in:
@@ -2,7 +2,10 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
@@ -10,8 +13,14 @@ using Windows.Storage.Streams;
|
|||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
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()
|
public FallbackOpenFileItem()
|
||||||
: base(new NoOpCommand(), Resources.Indexer_Find_Path_fallback_display_title)
|
: 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)
|
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))
|
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 item = new IndexerItem() { FullPath = query, FileName = Path.GetFileName(query) };
|
||||||
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.AsDefault);
|
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.AsDefault);
|
||||||
Command = listItemForUs.Command;
|
Command = listItemForUs.Command;
|
||||||
@@ -43,12 +64,65 @@ internal sealed partial class FallbackOpenFileItem : FallbackCommandItem
|
|||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Title = string.Empty;
|
_queryCookie++;
|
||||||
Subtitle = string.Empty;
|
|
||||||
Command = new NoOpCommand();
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Threading.Tasks;
|
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.CmdPal.Ext.Indexer.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using Windows.Storage.Streams;
|
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||||
|
|
||||||
internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
||||||
{
|
{
|
||||||
private readonly List<IListItem> _indexerListItems = [];
|
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()
|
public IndexerPage()
|
||||||
{
|
{
|
||||||
@@ -30,16 +27,31 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
|||||||
Icon = Icons.FileExplorer;
|
Icon = Icons.FileExplorer;
|
||||||
Name = Resources.Indexer_Title;
|
Name = Resources.Indexer_Title;
|
||||||
PlaceholderText = Resources.Indexer_PlaceholderText;
|
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)
|
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||||
{
|
{
|
||||||
if (oldSearch != newSearch)
|
if (oldSearch != newSearch && newSearch != initialQuery)
|
||||||
{
|
{
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
Query(newSearch);
|
Query(newSearch);
|
||||||
LoadMore();
|
LoadMore();
|
||||||
|
initialQuery = string.Empty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +61,9 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
|||||||
public override void LoadMore()
|
public override void LoadMore()
|
||||||
{
|
{
|
||||||
IsLoading = true;
|
IsLoading = true;
|
||||||
FetchItems(20);
|
var results = _searchEngine.FetchItems(_indexerListItems.Count, 20, _queryCookie, out var hasMore);
|
||||||
|
_indexerListItems.AddRange(results);
|
||||||
|
HasMoreItems = hasMore;
|
||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
RaiseItemsChanged(_indexerListItems.Count);
|
RaiseItemsChanged(_indexerListItems.Count);
|
||||||
}
|
}
|
||||||
@@ -58,70 +72,16 @@ internal sealed partial class IndexerPage : DynamicListPage, IDisposable
|
|||||||
{
|
{
|
||||||
++_queryCookie;
|
++_queryCookie;
|
||||||
_indexerListItems.Clear();
|
_indexerListItems.Clear();
|
||||||
_searchQuery.SearchResults.Clear();
|
|
||||||
_searchQuery.CancelOutstandingQueries();
|
|
||||||
|
|
||||||
if (query == string.Empty)
|
_searchEngine.Query(query, _queryCookie);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_searchQuery = null;
|
if (disposeSearchEngine)
|
||||||
GC.SuppressFinalize(this);
|
{
|
||||||
|
_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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to This file doesn't exist.
|
/// Looks up a localized string similar to This file doesn't exist.
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Search files on this device.
|
/// Looks up a localized string similar to Search files on this device.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -156,10 +156,25 @@
|
|||||||
<data name="Indexer_PlaceholderText" xml:space="preserve">
|
<data name="Indexer_PlaceholderText" xml:space="preserve">
|
||||||
<value>Search for files and folders...</value>
|
<value>Search for files and folders...</value>
|
||||||
</data>
|
</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">
|
<data name="Indexer_Subtitle" xml:space="preserve">
|
||||||
<value>Search files on this device</value>
|
<value>Search files on this device</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Indexer_Title" xml:space="preserve">
|
<data name="Indexer_Title" xml:space="preserve">
|
||||||
<value>Search files</value>
|
<value>Search files</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Indexer_fallback_searchPage_title" xml:space="preserve">
|
||||||
|
<value>Search for "{0}" in files</value>
|
||||||
|
</data>
|
||||||
</root>
|
</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