diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
index bd8d32ff511..3df2487427d 100644
--- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
+++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs
@@ -246,7 +246,7 @@ public static string ReturnPreviousDirectoryIfIncompleteString(string path)
/// Sub path
/// If , when and are equal, returns
///
- public static bool PathContains(string parentPath, string subPath, bool allowEqual = false)
+ public static bool PathContains(this string parentPath, string subPath, bool allowEqual = false)
{
var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath);
return (rel != "." || allowEqual)
diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
index e9d37433f4e..4639d431ae1 100644
--- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs
+++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs
@@ -57,7 +57,8 @@ public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestric
var result = QueryConstructor.TopLevelDirectoryConstraint(folderPath);
// Then
- Assert.IsTrue(result == expectedString,
+ Assert.AreEqual(result,
+ expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual: {result}{Environment.NewLine}");
}
@@ -74,18 +75,23 @@ public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_Then
var queryString = queryConstructor.Directory(folderPath);
// Then
- Assert.IsTrue(queryString.Replace(" ", " ") == expectedString.Replace(" ", " "),
+ Assert.AreEqual(queryString.Replace(" ", " "),
+ expectedString.Replace(" ", " "),
$"Expected string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {queryString}{Environment.NewLine}");
}
[SupportedOSPlatform("windows7.0")]
- [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")]
+ [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")]
public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString(
- string folderPath, string userSearchString, string expectedString)
+ string folderPath,
+ string userSearchString,
+ string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
@@ -109,12 +115,14 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryWhereR
}
[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("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")]
public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShouldUseExpectedString(
- string userSearchString, string expectedString)
+ string userSearchString,
+ string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
@@ -135,7 +143,8 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShould
[SupportedOSPlatform("windows7.0")]
[TestCase(@"some words", @"FREETEXT('some words')")]
public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSearch_ThenShouldReturnFreeTextString(
- string querySearchString, string expectedString)
+ string querySearchString,
+ string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
@@ -144,16 +153,19 @@ public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSe
var resultString = QueryConstructor.RestrictionsForFileContentSearch(querySearchString);
// Then
- Assert.IsTrue(resultString == expectedString,
+ Assert.AreEqual(resultString,
+ expectedString,
$"Expected QueryWhereRestrictions string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
[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")]
+ [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")]
public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString(
- string userSearchString, string expectedString)
+ string userSearchString,
+ string expectedString)
{
// Given
var queryConstructor = new QueryConstructor(new Settings());
@@ -162,7 +174,8 @@ public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseE
var resultString = queryConstructor.FileContent(userSearchString);
// Then
- Assert.IsTrue(resultString == expectedString,
+ Assert.AreEqual(resultString,
+ expectedString,
$"Expected query string: {expectedString}{Environment.NewLine} " +
$"Actual string was: {resultString}{Environment.NewLine}");
}
@@ -202,7 +215,8 @@ public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(
var result = FilesFolders.IsLocationPathString(querySearchString);
//Then
- Assert.IsTrue(result == expectedResult,
+ Assert.AreEqual(result,
+ expectedResult,
$"Expected query search string check result is: {expectedResult} {Environment.NewLine} " +
$"Actual check result is {result} {Environment.NewLine}");
@@ -212,10 +226,13 @@ public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(
[TestCase(@"C:\SomeFolder\SomeApp\SomeFile", true, @"C:\SomeFolder\SomeApp\")]
[TestCase(@"C:\NonExistentFolder\SomeApp", false, "")]
public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnThePreviousDirectoryPathString(
- string path, bool previousDirectoryExists, string expectedString)
+ string path,
+ bool previousDirectoryExists,
+ string expectedString)
{
// When
Func previousLocationExists = null;
+
if (previousDirectoryExists)
{
previousLocationExists = PreviousLocationExistsReturnsTrue;
@@ -229,7 +246,8 @@ public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnT
var previousDirectoryPath = FilesFolders.GetPreviousExistingDirectory(previousLocationExists, path);
//Then
- Assert.IsTrue(previousDirectoryPath == expectedString,
+ Assert.AreEqual(previousDirectoryPath,
+ expectedString,
$"Expected path string: {expectedString} {Environment.NewLine} " +
$"Actual path string is {previousDirectoryPath} {Environment.NewLine}");
}
@@ -237,12 +255,14 @@ public void GivenAPartialPath_WhenPreviousLevelDirectoryExists_ThenShouldReturnT
[TestCase(@"C:\NonExistentFolder\SomeApp", @"C:\NonExistentFolder\")]
[TestCase(@"C:\NonExistentFolder\SomeApp\", @"C:\NonExistentFolder\SomeApp\")]
public void WhenGivenAPath_ThenShouldReturnThePreviousDirectoryPathIfIncompleteOrOriginalString(
- string path, string expectedString)
+ string path,
+ string expectedString)
{
var returnedPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path);
//Then
- Assert.IsTrue(returnedPath == expectedString,
+ Assert.AreEqual(returnedPath,
+ expectedString,
$"Expected path string: {expectedString} {Environment.NewLine} " +
$"Actual path string is {returnedPath} {Environment.NewLine}");
}
@@ -280,23 +300,24 @@ public void GivenDirectoryInfoSearch_WhenSearchPatternHotKeyIsSearchAll_ThenSear
[TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "p", true, false, "p c:\\somefolder\\someotherfolder\\")]
[TestCase("c:\\somefolder\\someotherfolder", ResultType.Folder, "", true, true, "c:\\somefolder\\someotherfolder\\")]
public void GivenFolderResult_WhenGetPath_ThenPathShouldBeExpectedString(
- string path,
- ResultType type,
+ string path,
+ ResultType type,
string actionKeyword,
- bool pathSearchKeywordEnabled,
+ bool pathSearchKeywordEnabled,
bool searchActionKeywordEnabled,
string expectedResult)
{
// Given
- var settings = new Settings()
+ var settings = new Settings
{
PathSearchKeywordEnabled = pathSearchKeywordEnabled,
PathSearchActionKeyword = "p",
SearchActionKeywordEnabled = searchActionKeywordEnabled,
SearchActionKeyword = Query.GlobalPluginWildcardSign
};
+
ResultManager.Init(new PluginInitContext(), settings);
-
+
// When
var result = ResultManager.GetPathWithActionKeyword(path, type, actionKeyword);
@@ -317,13 +338,14 @@ public void GivenFileResult_WhenGetPath_ThenPathShouldBeExpectedString(
string expectedResult)
{
// Given
- var settings = new Settings()
+ var settings = new Settings
{
PathSearchKeywordEnabled = pathSearchKeywordEnabled,
PathSearchActionKeyword = "p",
SearchActionKeywordEnabled = searchActionKeywordEnabled,
SearchActionKeyword = "e"
};
+
ResultManager.Init(new PluginInitContext(), settings);
// When
@@ -346,8 +368,12 @@ public void GivenQueryWithFolderTypeResult_WhenGetAutoComplete_ThenResultShouldB
string expectedResult)
{
// Given
- var query = new Query() { ActionKeyword = actionKeyword };
- var settings = new Settings()
+ var query = new Query
+ {
+ ActionKeyword = actionKeyword
+ };
+
+ var settings = new Settings
{
PathSearchKeywordEnabled = pathSearchKeywordEnabled,
PathSearchActionKeyword = "p",
@@ -356,6 +382,7 @@ public void GivenQueryWithFolderTypeResult_WhenGetAutoComplete_ThenResultShouldB
QuickAccessActionKeyword = "q",
IndexSearchActionKeyword = "i"
};
+
ResultManager.Init(new PluginInitContext(), settings);
// When
@@ -378,8 +405,12 @@ public void GivenQueryWithFileTypeResult_WhenGetAutoComplete_ThenResultShouldBeE
string expectedResult)
{
// Given
- var query = new Query() { ActionKeyword = actionKeyword };
- var settings = new Settings()
+ var query = new Query
+ {
+ ActionKeyword = actionKeyword
+ };
+
+ var settings = new Settings
{
QuickAccessActionKeyword = "q",
IndexSearchActionKeyword = "i",
@@ -388,6 +419,7 @@ public void GivenQueryWithFileTypeResult_WhenGetAutoComplete_ThenResultShouldBeE
SearchActionKeywordEnabled = searchActionKeywordEnabled,
SearchActionKeyword = Query.GlobalPluginWildcardSign
};
+
ResultManager.Init(new PluginInitContext(), settings);
// When
@@ -404,15 +436,14 @@ public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(strin
{
// Given
var comparator = PathEqualityComparator.Instance;
- var result1 = new Result
+ var result1 = new SearchResult
{
- Title = Path.GetFileName(path1),
- SubTitle = path1
+ FullPath = path1
};
- var result2 = new Result
+
+ var result2 = new SearchResult
{
- Title = Path.GetFileName(path2),
- SubTitle = path2
+ FullPath = path2
};
// When, Then
@@ -425,22 +456,21 @@ public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, str
{
// Given
var comparator = PathEqualityComparator.Instance;
- var result1 = new Result
+ var result1 = new SearchResult
{
- Title = Path.GetFileName(path1),
- SubTitle = path1
+ FullPath = path1
};
- var result2 = new Result
+
+ var result2 = new SearchResult
{
- Title = Path.GetFileName(path2),
- SubTitle = path2
+ FullPath = path2
};
var hash1 = comparator.GetHashCode(result1);
var hash2 = comparator.GetHashCode(result2);
// When, Then
- Assert.IsTrue(hash1 == hash2);
+ Assert.AreEqual(hash1, hash2);
}
[TestCase(@"%appdata%", true)]
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
index 82a5d544122..5ca8e201294 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs
@@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
@@ -42,11 +43,13 @@ public Task InitAsync(PluginInitContext context)
contextMenu = new ContextMenu(Context, Settings, viewModel);
searchManager = new SearchManager(Settings, Context);
ResultManager.Init(Context, Settings);
-
+
SortOptionTranslationHelper.API = context.API;
- EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory, "EverythingSDK",
+ EverythingApiDllImport.Load(Path.Combine(Context.CurrentPluginMetadata.PluginDirectory,
+ "EverythingSDK",
Environment.Is64BitProcess ? "x64" : "x86"));
+
return Task.CompletedTask;
}
@@ -59,7 +62,10 @@ public async Task> QueryAsync(Query query, CancellationToken token)
{
try
{
- return await searchManager.SearchAsync(query, token);
+ // null means cancelled
+ return (await searchManager.SearchAsync(query, token))?
+ .Select(r => ResultManager.CreateResult(query, r))
+ .ToList();
}
catch (Exception e) when (e is SearchException or EngineNotAvailableException)
{
@@ -75,11 +81,12 @@ public async Task> QueryAsync(Query query, CancellationToken token)
IcoPath = e is EngineNotAvailableException { ErrorIcon: { } iconPath }
? iconPath
: Constants.GeneralSearchErrorImagePath,
- AsyncAction = e is EngineNotAvailableException {Action: { } action}
+ AsyncAction = e is EngineNotAvailableException { Action: { } action }
? action
: _ =>
{
Clipboard.SetDataObject(e.ToString());
+
return new ValueTask(true);
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs
index e526fb85a1f..d93162f53d6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/EnvironmentVariables.cs
@@ -18,6 +18,7 @@ private static Dictionary EnvStringPaths
{
LoadEnvironmentStringPaths();
}
+
return _envStringPaths;
}
}
@@ -25,19 +26,20 @@ private static Dictionary EnvStringPaths
internal static bool IsEnvironmentVariableSearch(string search)
{
return search.StartsWith("%")
- && search != "%%"
- && !search.Contains('\\')
- && EnvStringPaths.Count > 0;
+ && search != "%%"
+ && !search.Contains('\\')
+ && EnvStringPaths.Count > 0;
}
public static bool HasEnvironmentVar(string search)
{
// "c:\foo %appdata%\" returns false
var splited = search.Split(Path.DirectorySeparatorChar);
- return splited.Any(dir => dir.StartsWith('%') &&
- dir.EndsWith('%') &&
- dir.Length > 2 &&
- dir.Split('%').Length == 3);
+
+ return splited.Any(dir => dir.StartsWith('%') &&
+ dir.EndsWith('%') &&
+ dir.Length > 2 &&
+ dir.Split('%').Length == 3);
}
private static void LoadEnvironmentStringPaths()
@@ -66,10 +68,8 @@ private static void LoadEnvironmentStringPaths()
}
}
- internal static List GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
+ internal static IEnumerable GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
{
- var results = new List();
-
var search = querySearch;
if (querySearch.EndsWith("%") && search.Length > 1)
@@ -81,30 +81,28 @@ internal static List GetEnvironmentStringPathSuggestions(string querySea
{
var expandedPath = EnvStringPaths[search];
- results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
-
- return results;
+ yield return new SearchResult
+ {
+ Name = $"%{search}%", FullPath = expandedPath
+ };
}
}
- if (querySearch == "%")
- {
- search = ""; // Get all paths
- }
- else
- {
- search = search.Substring(1);
- }
+ ReadOnlyMemory slice = querySearch == "%" ? "".AsMemory() : // Get all paths
+ search.AsMemory()[1..];
- foreach (var p in EnvStringPaths)
+ foreach (var pair in EnvStringPaths)
{
- if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
+ if (pair.Key.AsSpan().StartsWith(slice.Span, StringComparison.InvariantCultureIgnoreCase))
{
- results.Add(ResultManager.CreateFolderResult($"%{p.Key}%", p.Value, p.Value, query));
+ yield return new SearchResult
+ {
+ Name = $"%{pair.Key}%",
+ FullPath = pair.Value,
+ Type = ResultType.EnvironmentalVariable
+ };
}
}
-
- return results;
}
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
index cdd2c93e69c..b9264bd4d02 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/QuickAccess.cs
@@ -8,36 +8,35 @@ internal static class QuickAccess
{
private const int quickAccessResultScore = 100;
- internal static List AccessLinkListMatched(Query query, IEnumerable accessLinks)
+ internal static IEnumerable AccessLinkListMatched(Query query, IEnumerable accessLinks)
{
if (string.IsNullOrEmpty(query.Search))
- return new List();
+ return Enumerable.Empty();
string search = query.Search.ToLower();
- var queriedAccessLinks =
- accessLinks
+ return accessLinks
.Where(x => x.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || x.Path.Contains(search, StringComparison.OrdinalIgnoreCase))
.OrderBy(x => x.Type)
- .ThenBy(x => x.Name);
-
- return queriedAccessLinks.Select(l => l.Type switch
- {
- ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query, quickAccessResultScore),
- ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
- _ => throw new ArgumentOutOfRangeException()
- }).ToList();
+ .ThenBy(x => x.Name)
+ .Select(x => new SearchResult()
+ {
+ FullPath = x.Path,
+ Score = quickAccessResultScore,
+ Type = x.Type,
+ WindowsIndexed = false
+ });
}
- internal static List AccessLinkListAll(Query query, IEnumerable accessLinks)
+ internal static IEnumerable AccessLinkListAll(Query query, IEnumerable accessLinks)
=> accessLinks
.OrderBy(x => x.Type)
.ThenBy(x => x.Name)
- .Select(l => l.Type switch
+ .Select(l => new SearchResult()
{
- ResultType.Folder => ResultManager.CreateFolderResult(l.Name, l.Path, l.Path, query),
- ResultType.File => ResultManager.CreateFileResult(l.Path, query, quickAccessResultScore),
- _ => throw new ArgumentOutOfRangeException()
- }).ToList();
+ FullPath = l.Path,
+ Type = l.Type,
+ Score = quickAccessResultScore
+ });
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
index ed4f39735bd..5f4eb484e0e 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs
@@ -13,6 +13,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
public static class ResultManager
{
private static PluginInitContext Context;
+
private static Settings Settings { get; set; }
public static void Init(PluginInitContext context, Settings settings)
@@ -57,15 +58,30 @@ public static Result CreateResult(Query query, SearchResult result)
{
return result.Type switch
{
- ResultType.Folder or ResultType.Volume => CreateFolderResult(Path.GetFileName(result.FullPath),
- result.FullPath, result.FullPath, query, 0, result.WindowsIndexed),
+ ResultType.Folder => CreateFolderResult(Path.GetFileName(result.FullPath),
+ result.FullPath,
+ result.FullPath,
+ query,
+ 0,
+ result.WindowsIndexed),
+ ResultType.CurrentFolder => CreateOpenCurrentFolderResult(result.FullPath, query.ActionKeyword, result.WindowsIndexed),
+ ResultType.Volume => CreateDriveSpaceDisplayResult(result.FullPath,
+ query.ActionKeyword,
+ result.WindowsIndexed),
ResultType.File => CreateFileResult(
- result.FullPath, query, 0, result.WindowsIndexed),
+ result.FullPath,
+ query,
+ 0,
+ result.WindowsIndexed),
+ ResultType.EnvironmentalVariable => CreateFolderResult(result.Name,
+ result.FullPath,
+ result.FullPath,
+ query),
_ => throw new ArgumentOutOfRangeException()
};
}
- internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool windowsIndexed = false)
+ private static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool windowsIndexed = false)
{
return new Result
{
@@ -82,11 +98,13 @@ internal static Result CreateFolderResult(string title, string subtitle, string
try
{
Context.API.OpenDirectory(path);
+
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Could not start " + path);
+
return false;
}
}
@@ -136,6 +154,7 @@ internal static Result CreateDriveSpaceDisplayResult(string path, string actionK
Action = c =>
{
Context.API.OpenDirectory(path);
+
return true;
},
TitleToolTip = path,
@@ -171,6 +190,7 @@ private static string ToReadableSize(long pDrvSize, int pi)
Space = " TB";
var returnStr = $"{Convert.ToInt32(drvSize)}{Space}";
+
if (mok != 0)
{
returnStr = pi switch
@@ -202,6 +222,7 @@ internal static Result CreateOpenCurrentFolderResult(string path, string actionK
Action = _ =>
{
Context.API.OpenDirectory(folderPath);
+
return true;
},
ContextData = new SearchResult
@@ -213,7 +234,7 @@ internal static Result CreateOpenCurrentFolderResult(string path, string actionK
};
}
- internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
+ private static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
{
Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo
{
@@ -281,22 +302,16 @@ internal static Result CreateFileResult(string filePath, Query query, int score
WindowsIndexed = windowsIndexed
}
};
+
return result;
}
- public static bool IsMedia(string extension)
+ private static bool IsMedia(string extension)
{
- if (string.IsNullOrEmpty(extension))
- {
- return false;
- }
- else
- {
- return MediaExtensions.Contains(extension.ToLowerInvariant());
- }
+ return !string.IsNullOrEmpty(extension) && MediaExtensions.Contains(extension.ToLowerInvariant());
}
- public static readonly string[] MediaExtensions =
+ private static readonly string[] MediaExtensions =
{
".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4"
};
@@ -306,6 +321,8 @@ public enum ResultType
{
Volume,
Folder,
- File
+ CurrentFolder,
+ File,
+ EnvironmentalVariable
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
index 51c4c3d9d54..d7038c363be 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
@@ -1,9 +1,9 @@
using Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo;
-using Flow.Launcher.Plugin.Explorer.Search.Everything;
using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -26,92 +26,87 @@ public SearchManager(Settings settings, PluginInitContext context)
///
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
///
- public class PathEqualityComparator : IEqualityComparer
+ public class PathEqualityComparator : IEqualityComparer
{
private static PathEqualityComparator instance;
+
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
- public bool Equals(Result x, Result y)
+ public bool Equals(SearchResult x, SearchResult y)
{
- return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
- && string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase);
+ return x.FullPath.Equals(y.FullPath, StringComparison.InvariantCultureIgnoreCase);
}
- public int GetHashCode(Result obj)
+ public int GetHashCode(SearchResult obj)
{
- return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? "");
+ return StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.FullPath);
}
}
- internal async Task> SearchAsync(Query query, CancellationToken token)
+ internal async Task> SearchAsync(Query query, CancellationToken token)
{
- var results = new HashSet(PathEqualityComparator.Instance);
-
- // This allows the user to type the below action keywords and see/search the list of quick folder links
- if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword))
- {
- if (string.IsNullOrEmpty(query.Search) && ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword))
- return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks);
+ var results = new HashSet(PathEqualityComparator.Instance);
- var quickAccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
+ var task = GetQueryTask(query);
- results.UnionWith(quickAccessLinks);
- }
- else
+ // This allows the user to type the below action keywords and see/search the list of quick folder links
+ if (task.HasFlag(SearchTask.QuickAccessSearch))
{
- return new List();
+ if (string.IsNullOrEmpty(query.Search))
+ {
+ results.UnionWith(QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks));
+ }
+ else
+ {
+ var quickAccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
+ results.UnionWith(quickAccessLinks);
+ }
}
- IAsyncEnumerable searchResults;
+ IAsyncEnumerable searchResults = null;
bool isPathSearch = query.Search.IsLocationPathString() || IsEnvironmentVariableSearch(query.Search);
- string engineName;
+ string engineName = "";
- switch (isPathSearch)
+ if (task.HasFlag(SearchTask.PathSearch) && isPathSearch)
{
- case true
- when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
-
- results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
-
- return results.ToList();
-
- case false
- when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
+ await foreach (var path in PathSearchAsync(query, token).ConfigureAwait(false))
+ {
+ results.Add(path);
+ }
- // Intentionally require enabling of Everything's content search due to its slowness
- if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
- return EverythingContentSearchResult(query);
+ return results.ToList();
+ }
- searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
- engineName = Enum.GetName(Settings.ContentSearchEngine);
- break;
+ if (task.HasFlag(SearchTask.IndexSearch))
+ {
+ searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
+ engineName = Enum.GetName(Settings.IndexSearchEngine);
+ }
- case false
- when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
+ if (task.HasFlag(SearchTask.FileContentSearch))
+ {
+ if (!Settings.EnableEverythingContentSearch && Settings.ContentSearchEngine == Settings.ContentIndexSearchEngineOption.Everything)
+ ThrowEverythingContentSearchUnavailable(query);
- searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
- engineName = Enum.GetName(Settings.IndexSearchEngine);
- break;
- default:
- return results.ToList();
+ searchResults = Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token);
+ engineName = Enum.GetName(Settings.ContentSearchEngine);
}
try
{
- await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
- results.Add(ResultManager.CreateResult(query, search));
+ if (searchResults != null)
+ {
+ await foreach (var result in searchResults.WithCancellation(token))
+ {
+ results.Add(result);
+ }
+ }
}
catch (OperationCanceledException)
{
- return new List();
+ return null;
}
catch (EngineNotAvailableException)
{
@@ -123,11 +118,33 @@ when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
}
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
- excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle)));
+ excludedPath => excludedPath.Path.PathContains(r.FullPath)));
return results.ToList();
}
+ private SearchTask GetQueryTask(Query query)
+ {
+ SearchTask task = SearchTask.None;
+
+ if (ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword))
+ task |= SearchTask.IndexSearch | SearchTask.PathSearch | SearchTask.QuickAccessSearch;
+
+ if (ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword))
+ task |= SearchTask.QuickAccessSearch;
+
+ if (ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword))
+ task |= SearchTask.PathSearch;
+
+ if (ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword))
+ task |= SearchTask.IndexSearch;
+
+ if (ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword))
+ task |= SearchTask.FileContentSearch;
+
+ return task;
+ }
+
private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActionKeyword)
{
var keyword = query.ActionKeyword.Length == 0 ? Query.GlobalPluginWildcardSign : query.ActionKeyword;
@@ -148,33 +165,35 @@ private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActio
};
}
- private List EverythingContentSearchResult(Query query)
+ [DoesNotReturn]
+ private void ThrowEverythingContentSearchUnavailable(Query query)
{
- return new List()
- {
- new()
+ throw new EngineNotAvailableException(nameof(Settings.ContentIndexSearchEngineOption.Everything),
+ Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
+ Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
+ _ =>
{
- Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
- SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
- IcoPath = "Images/index_error.png",
- Action = c =>
- {
- Settings.EnableEverythingContentSearch = true;
- Context.API.ChangeQuery(query.RawQuery, true);
- return false;
- }
- }
- };
+ Settings.EnableEverythingContentSearch = true;
+ Context.API.ChangeQuery(query.RawQuery, true);
+
+ return ValueTask.FromResult(false);
+ });
+
}
- private async Task> PathSearchAsync(Query query, CancellationToken token = default)
+ private async IAsyncEnumerable PathSearchAsync(Query query, CancellationToken token = default)
{
var querySearch = query.Search;
- var results = new HashSet(PathEqualityComparator.Instance);
-
if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
- return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
+ {
+ foreach (var envResult in EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context))
+ {
+ yield return envResult;
+ }
+
+ yield break;
+ }
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
@@ -182,19 +201,25 @@ private async Task> PathSearchAsync(Query query, CancellationToken
// Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
- return results.ToList();
+ yield break;
var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex
&& UseWindowsIndexForDirectorySearch(locationPath);
var retrievedDirectoryPath = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath);
- results.Add(retrievedDirectoryPath.EndsWith(":\\")
- ? ResultManager.CreateDriveSpaceDisplayResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch)
- : ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch));
+ yield return new SearchResult()
+ {
+ FullPath = retrievedDirectoryPath,
+ Type = retrievedDirectoryPath.EndsWith(":\\")
+ ? ResultType.Volume
+ : ResultType.CurrentFolder,
+ Score = 100,
+ WindowsIndexed = useIndexSearch
+ };
if (token.IsCancellationRequested)
- return new List();
+ yield break;
IAsyncEnumerable directoryResult;
@@ -208,7 +233,6 @@ private async Task> PathSearchAsync(Query query, CancellationToken
query.Search[(recursiveIndicatorIndex + 1)..],
true,
token);
-
}
else
{
@@ -216,22 +240,12 @@ private async Task> PathSearchAsync(Query query, CancellationToken
}
if (token.IsCancellationRequested)
- return new List();
+ yield break;
- try
+ await foreach (var directory in directoryResult.WithCancellation(token).ConfigureAwait(false))
{
- await foreach (var directory in directoryResult.WithCancellation(token).ConfigureAwait(false))
- {
- results.Add(ResultManager.CreateResult(query, directory));
- }
+ yield return directory;
}
- catch (Exception e)
- {
- throw new SearchException(Enum.GetName(Settings.PathEnumerationEngine), e.Message, e);
- }
-
-
- return results.ToList();
}
public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
@@ -246,11 +260,21 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath)
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
}
- internal static bool IsEnvironmentVariableSearch(string search)
+ private static bool IsEnvironmentVariableSearch(string search)
{
return search.StartsWith("%")
&& search != "%%"
&& !search.Contains('\\');
}
}
+
+ [Flags]
+ internal enum SearchTask
+ {
+ None = 0,
+ IndexSearch = 1 << 0,
+ QuickAccessSearch = 1 << 1,
+ PathSearch = 1 << 2,
+ FileContentSearch = 1 << 3,
+ }
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs
index 92c24559d6e..44315bb7598 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchResult.cs
@@ -4,6 +4,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
{
public record struct SearchResult
{
+ public string Name { get; init; }
public string FullPath { get; init; }
public ResultType Type { get; init; }
public int Score { get; init; }