Merge pull request #567 from pc223/SearchOrderByRank

Introduce new search order - System.Search.Rank - for Explorer plugin
This commit is contained in:
Jack Ye 2025-04-12 21:20:58 +08:00 committed by GitHub
commit 9414710552
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 27 additions and 26 deletions

View file

@ -39,8 +39,8 @@ namespace Flow.Launcher.Test.Plugins
}
[SupportedOSPlatform("windows7.0")]
[TestCase("C:\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY System.FileName")]
[TestCase("C:\\SomeFolder\\", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY System.FileName")]
[TestCase("C:\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
[TestCase("C:\\SomeFolder\\", $"SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType FROM SystemIndex WHERE directory='file:C:\\SomeFolder\\' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString)
{
// Given
@ -59,7 +59,7 @@ namespace Flow.Launcher.Test.Plugins
[TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType" +
" FROM SystemIndex WHERE directory='file:C:\\SomeFolder'" +
" AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" +
" ORDER BY System.FileName")]
$" ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
string folderPath, string userSearchString, string expectedString)
{
@ -87,8 +87,8 @@ namespace Flow.Launcher.Test.Plugins
[SupportedOSPlatform("windows7.0")]
[TestCase("flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" " +
"FROM \"SystemIndex\" WHERE (System.FileName LIKE 'flow.launcher.sln%' " +
"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY System.FileName")]
[TestCase("", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY System.FileName")]
$"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033)) AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
[TestCase("", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{
@ -107,7 +107,6 @@ namespace Flow.Launcher.Test.Plugins
ClassicAssert.AreEqual(expectedString, resultString);
}
[SupportedOSPlatform("windows7.0")]
[TestCase(@"some words", @"FREETEXT('some words')")]
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
@ -127,7 +126,7 @@ namespace Flow.Launcher.Test.Plugins
[SupportedOSPlatform("windows7.0")]
[TestCase("some words", "SELECT TOP 100 System.FileName, System.ItemUrl, System.ItemType " +
"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY System.FileName")]
$"FROM SystemIndex WHERE FREETEXT('some words') AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
string userSearchString, string expectedString)
{

View file

@ -6,16 +6,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
{
public class QueryConstructor
{
private static Regex _specialCharacterMatcher = new(@"[\@\\#\\&\_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled);
private static Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled);
private static readonly Regex _specialCharacterMatcher = new(@"[\@\\#\\&\_;,\%\|\!\(\)\{\}\[\]\^\~\?\\""\/\:\=\-]+", RegexOptions.Compiled);
private static readonly Regex _multiWhiteSpacesMatcher = new(@"\s+", RegexOptions.Compiled);
private Settings settings { get; }
private Settings Settings { get; }
private const string SystemIndex = "SystemIndex";
public QueryConstructor(Settings settings)
{
this.settings = settings;
Settings = settings;
}
public CSearchQueryHelper CreateBaseQuery()
@ -23,7 +23,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
var baseQuery = CreateQueryHelper();
// Set the number of results we want. Don't set this property if all results are needed.
baseQuery.QueryMaxResults = settings.MaxResult;
baseQuery.QueryMaxResults = Settings.MaxResult;
// Set list of columns we want to display, getting the path presently
baseQuery.QuerySelectColumns = "System.FileName, System.ItemUrl, System.ItemType";
@ -37,7 +37,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
return baseQuery;
}
internal CSearchQueryHelper CreateQueryHelper()
internal static CSearchQueryHelper CreateQueryHelper()
{
// This uses the Microsoft.Search.Interop assembly
// Throws COMException if Windows Search service is not running/disabled, this needs to be caught
@ -54,20 +54,19 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
public static string TopLevelDirectoryConstraint(ReadOnlySpan<char> path) => $"directory='file:{path}'";
public static string RecursiveDirectoryConstraint(ReadOnlySpan<char> path) => $"scope='file:{path}'";
///<summary>
/// Search will be performed on all folders and files on the first level of a specified directory.
///</summary>
public string Directory(ReadOnlySpan<char> path, ReadOnlySpan<char> searchString = default, bool recursive = false)
{
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND ({FileName} LIKE '{searchString}%' OR CONTAINS({FileName},'\"{searchString}*\"'))";
var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND (System.FileName LIKE '{searchString}%' OR CONTAINS(System.FileName,'\"{searchString}*\"'))";
var scopeConstraint = recursive
? RecursiveDirectoryConstraint(path)
: TopLevelDirectoryConstraint(path);
var query = $"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {FileName}";
var query = $"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {scopeConstraint} {queryConstraint} ORDER BY {OrderIdentifier}";
return query;
}
@ -84,7 +83,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
var replacedSearchString = ReplaceSpecialCharacterWithTwoSideWhiteSpace(userSearchString);
// Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}";
}
/// <summary>
@ -121,10 +120,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
public const string RestrictionsForAllFilesAndFoldersSearch = "scope='file:'";
/// <summary>
/// Order identifier: file name
/// Order identifier: System.Search.Rank DESC
/// </summary>
public const string FileName = "System.FileName";
/// <remarks>
/// <see href="https://docs.microsoft.com/en-us/windows/win32/properties/props-system-search-rank"/>
/// </remarks>
public const string OrderIdentifier = "System.Search.Rank DESC";
///<summary>
/// Search will be performed on all indexed file contents for the specified search keywords.
@ -132,7 +133,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
public string FileContent(ReadOnlySpan<char> userSearchString)
{
string query =
$"SELECT TOP {settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {FileName}";
$"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}";
return query;
}

View file

@ -82,14 +82,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
return HandledEngineNotAvailableExceptionAsync();
}
}
public IAsyncEnumerable<SearchResult> SearchAsync(string search, CancellationToken token)
{
return WindowsIndexFilesAndFoldersSearchAsync(search, token: token);
}
public IAsyncEnumerable<SearchResult> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token)
{
return WindowsIndexFileContentSearchAsync(contentSearch, token);
}
public IAsyncEnumerable<SearchResult> EnumerateAsync(string path, string search, bool recursive, CancellationToken token)
{
return WindowsIndexTopLevelFolderSearchAsync(search, path, recursive, token);
@ -100,19 +103,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
if (!Settings.WarnWindowsSearchServiceOff)
return AsyncEnumerable.Empty<SearchResult>();
var api = Main.Context.API;
throw new EngineNotAvailableException(
"Windows Index",
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
Constants.WindowsIndexErrorImagePath,
c =>
{
Settings.WarnWindowsSearchServiceOff = false;
// Clears the warning message so user is not mistaken that it has not worked
api.ChangeQuery(string.Empty);
Main.Context.API.ChangeQuery(string.Empty);
return ValueTask.FromResult(false);
});