diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
index c40040df5be..45c0d72acd9 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml
@@ -58,6 +58,8 @@
File Content Search:
Index Search:
Quick Access:
+ Folder Search:
+ File Search:
Current Action Keyword
Done
Enabled
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
index 21a6945a8d9..a37707ec76a 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs
@@ -1,13 +1,15 @@
-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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using System.Windows.Documents;
using Flow.Launcher.Plugin.Explorer.Exceptions;
+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 static Flow.Launcher.Plugin.Explorer.Settings;
using Path = System.IO.Path;
namespace Flow.Launcher.Plugin.Explorer.Search
@@ -18,6 +20,13 @@ public class SearchManager
internal Settings Settings;
+ private readonly Dictionary> _typesToFilterByActionKeyword = new()
+ {
+ { ActionKeyword.FileSearchActionKeyword, [ResultType.Folder, ResultType.Volume] },
+ { ActionKeyword.FolderSearchActionKeyword, [ResultType.File] },
+ };
+
+
public SearchManager(Settings settings, PluginInitContext context)
{
Context = context;
@@ -31,7 +40,7 @@ public class PathEqualityComparator : IEqualityComparer
{
private static PathEqualityComparator instance;
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
-
+
public bool Equals(Result x, Result y)
{
return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
@@ -48,44 +57,56 @@ internal async Task> SearchAsync(Query query, CancellationToken tok
{
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))
+ var keyword = query.ActionKeyword.Length == 0 ? Query.GlobalPluginWildcardSign : query.ActionKeyword;
+
+ // No action keyword matched - plugin should not handle this query, return empty results.
+ var activeActionKeywords = Settings.GetActiveActionKeywords(keyword);
+ if (activeActionKeywords.Count == 0)
{
- if (string.IsNullOrEmpty(query.Search) && ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword))
- return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks);
+ return [];
}
- else
+
+ var isPathSearch = query.Search.IsLocationPathString()
+ || EnvironmentVariables.IsEnvironmentVariableSearch(query.Search)
+ || EnvironmentVariables.HasEnvironmentVar(query.Search);
+
+ var queryIsEmpty = string.IsNullOrEmpty(query.Search);
+
+ if (queryIsEmpty && activeActionKeywords.ContainsKey(ActionKeyword.QuickAccessActionKeyword))
{
- // No action keyword matched- plugin should not handle this query, return empty results.
- return new List();
+ return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks);
}
- IAsyncEnumerable searchResults;
+ // If query is empty and active keyword is folder or search, return empty results.
+ if (queryIsEmpty && (activeActionKeywords.ContainsKey(ActionKeyword.FolderSearchActionKeyword)
+ || activeActionKeywords.ContainsKey(ActionKeyword.FileSearchActionKeyword)))
+ {
+ return [];
+ }
- bool isPathSearch = query.Search.IsLocationPathString()
- || EnvironmentVariables.IsEnvironmentVariableSearch(query.Search)
- || EnvironmentVariables.HasEnvironmentVar(query.Search);
+ // When file search is active, do not include path search in the active keywords.
+ // This prevents unwanted PathSearch results (e.g., drives or raw volume paths).
+ if (isPathSearch && !activeActionKeywords.ContainsKey(ActionKeyword.PathSearchActionKeyword)
+ && !activeActionKeywords.ContainsKey(ActionKeyword.FileSearchActionKeyword))
+ {
+ activeActionKeywords.Add(ActionKeyword.PathSearchActionKeyword, keyword);
+ }
+
+ IAsyncEnumerable searchResults;
string engineName;
switch (isPathSearch)
{
case true
- when ActionKeywordMatch(query, Settings.ActionKeyword.PathSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
-
+ when activeActionKeywords.ContainsKey(ActionKeyword.PathSearchActionKeyword)
+ || activeActionKeywords.ContainsKey(ActionKeyword.SearchActionKeyword):
results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false));
-
- return results.ToList();
+ return [.. results];
case false
- when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword):
-
// Intentionally require enabling of Everything's content search due to its slowness
+ when activeActionKeywords.ContainsKey(ActionKeyword.FileContentSearchActionKeyword):
if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch)
return EverythingContentSearchResult(query);
@@ -93,37 +114,37 @@ when ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKey
engineName = Enum.GetName(Settings.ContentSearchEngine);
break;
- case false
- when ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword)
- || ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword):
+ case true or false
+ when activeActionKeywords.ContainsKey(ActionKeyword.QuickAccessActionKeyword):
+ return QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
+
+ case false
+ when CanUseIndexSearchByActionKeywords(activeActionKeywords):
searchResults = Settings.IndexProvider.SearchAsync(query.Search, token);
engineName = Enum.GetName(Settings.IndexSearchEngine);
break;
-
- case true or false
- when ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword):
- return QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks);
-
default:
- return results.ToList();
- }
+ return [..results];
- // Merge Quick Access Link results for non-path searches.
- results.UnionWith(QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks));
+ }
+ //Merge Quick Access Link results for non-path searches.
+ results.UnionWith(GetQuickAccessResultsFilteredByActionKeyword(query, activeActionKeywords));
try
{
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
- if (search.Type == ResultType.File && IsExcludedFile(search)) {
+ {
+ if (ShouldSkipResultByTypeAndActionKeyword(activeActionKeywords, search))
+ {
continue;
- } else {
- results.Add(ResultManager.CreateResult(query, search));
}
+ results.Add(ResultManager.CreateResult(query, search));
+ }
}
catch (OperationCanceledException)
{
- return new List();
+ return [.. results];
}
catch (EngineNotAvailableException)
{
@@ -137,33 +158,13 @@ when ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword):
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle, allowEqual: true)));
- return results.ToList();
- }
-
- private bool ActionKeywordMatch(Query query, Settings.ActionKeyword allowedActionKeyword)
- {
- var keyword = query.ActionKeyword.Length == 0 ? Query.GlobalPluginWildcardSign : query.ActionKeyword;
-
- return allowedActionKeyword switch
- {
- Settings.ActionKeyword.SearchActionKeyword => Settings.SearchActionKeywordEnabled &&
- keyword == Settings.SearchActionKeyword,
- Settings.ActionKeyword.PathSearchActionKeyword => Settings.PathSearchKeywordEnabled &&
- keyword == Settings.PathSearchActionKeyword,
- Settings.ActionKeyword.FileContentSearchActionKeyword => Settings.FileContentSearchKeywordEnabled &&
- keyword == Settings.FileContentSearchActionKeyword,
- Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled &&
- keyword == Settings.IndexSearchActionKeyword,
- Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled &&
- keyword == Settings.QuickAccessActionKeyword,
- _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range")
- };
+ return [.. results];
}
private List EverythingContentSearchResult(Query query)
{
- return new List()
- {
+ return
+ [
new()
{
Title = Localize.flowlauncher_plugin_everything_enable_content_search(),
@@ -176,7 +177,7 @@ private List EverythingContentSearchResult(Query query)
return false;
}
}
- };
+ ];
}
private async Task> PathSearchAsync(Query query, CancellationToken token = default)
@@ -197,7 +198,7 @@ private async Task> PathSearchAsync(Query query, CancellationToken
// Check that actual location exists, otherwise directory search will throw directory not found exception
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(path).LocationExists())
- return results.ToList();
+ return [.. results];
var useIndexSearch = Settings.IndexSearchEngine is Settings.IndexSearchEngineOption.WindowsIndex
&& UseWindowsIndexForDirectorySearch(path);
@@ -209,7 +210,7 @@ private async Task> PathSearchAsync(Query query, CancellationToken
: ResultManager.CreateOpenCurrentFolderResult(retrievedDirectoryPath, query.ActionKeyword, useIndexSearch));
if (token.IsCancellationRequested)
- return new List();
+ return [.. results];
IAsyncEnumerable directoryResult;
@@ -231,7 +232,7 @@ private async Task> PathSearchAsync(Query query, CancellationToken
}
if (token.IsCancellationRequested)
- return new List();
+ return [.. results];
try
{
@@ -246,14 +247,14 @@ private async Task> PathSearchAsync(Query query, CancellationToken
}
- return results.ToList();
+ return [.. results];
}
public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
public static bool UseIndexSearch(string path)
{
- if (Main.Settings.IndexSearchEngine is not Settings.IndexSearchEngineOption.WindowsIndex)
+ if (Main.Settings.IndexSearchEngine is not IndexSearchEngineOption.WindowsIndex)
return false;
// Check if the path is using windows index search
@@ -275,10 +276,67 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath)
private bool IsExcludedFile(SearchResult result)
{
- string[] excludedFileTypes = Settings.ExcludedFileTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] excludedFileTypes = Settings.ExcludedFileTypes.Split([','], StringSplitOptions.RemoveEmptyEntries);
string fileExtension = Path.GetExtension(result.FullPath).TrimStart('.');
return excludedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase);
}
+
+ private bool ShouldSkipResultByTypeAndActionKeyword(Dictionary actions, SearchResult search)
+ {
+ // Is excluded file type
+ if (search.Type == ResultType.File && IsExcludedFile(search))
+ {
+ return true;
+ }
+ return IsResultTypeFilteredByActionKeyword(search.Type, actions);
+
+ }
+
+ private List GetQuickAccessResultsFilteredByActionKeyword(Query query, Dictionary actions)
+ {
+ var results = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks) ?? [];
+ if (results.Count == 0)
+ {
+ return results;
+ }
+
+ return results
+ .Where(r => r.ContextData is SearchResult result
+ && !IsResultTypeFilteredByActionKeyword(result.Type, actions))
+ .ToList();
+ }
+ private bool IsResultTypeFilteredByActionKeyword(ResultType type, Dictionary actions)
+ {
+ foreach (var action in actions.Keys)
+ {
+ if (_typesToFilterByActionKeyword.TryGetValue(action, out var typesToFilter))
+ {
+ return typesToFilter.Contains(type);
+ }
+ }
+
+ return false;
+ }
+
+ private bool CanUseIndexSearchByActionKeywords(Dictionary actions)
+ {
+ List keysToUseSearch =
+ [
+ ActionKeyword.FileSearchActionKeyword,
+ ActionKeyword.FolderSearchActionKeyword,
+ ActionKeyword.IndexSearchActionKeyword,
+ ActionKeyword.SearchActionKeyword
+ ];
+ foreach (var key in keysToUseSearch)
+ {
+ var contains = actions.ContainsKey(key);
+ if (!contains) continue;
+ return contains;
+ }
+
+ return false;
+ }
+
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
index 8d62531cd62..e65c03e9be1 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs
@@ -1,12 +1,13 @@
-using Flow.Launcher.Plugin.Explorer.Search;
-using Flow.Launcher.Plugin.Explorer.Search.Everything;
-using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
-using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
-using System;
+using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text.Json.Serialization;
+using Flow.Launcher.Plugin.Explorer.Search;
+using Flow.Launcher.Plugin.Explorer.Search.Everything;
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
+using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks;
+using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex;
namespace Flow.Launcher.Plugin.Explorer
{
@@ -58,6 +59,15 @@ public class Settings
public bool QuickAccessKeywordEnabled { get; set; }
+
+ public string FolderSearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
+
+ public bool FolderSearchKeywordEnabled { get; set; }
+
+ public string FileSearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign;
+
+ public bool FileSearchKeywordEnabled { get; set; }
+
public bool WarnWindowsSearchServiceOff { get; set; } = true;
public bool ShowFileSizeInPreviewPanel { get; set; } = true;
@@ -160,7 +170,9 @@ internal enum ActionKeyword
PathSearchActionKeyword,
FileContentSearchActionKeyword,
IndexSearchActionKeyword,
- QuickAccessActionKeyword
+ QuickAccessActionKeyword,
+ FolderSearchActionKeyword,
+ FileSearchActionKeyword,
}
internal string GetActionKeyword(ActionKeyword actionKeyword) => actionKeyword switch
@@ -170,6 +182,8 @@ internal enum ActionKeyword
ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword,
ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword,
ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword,
+ ActionKeyword.FolderSearchActionKeyword => FolderSearchActionKeyword,
+ ActionKeyword.FileSearchActionKeyword => FileSearchActionKeyword,
_ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyWord property not found")
};
@@ -180,6 +194,8 @@ internal enum ActionKeyword
ActionKeyword.FileContentSearchActionKeyword => FileContentSearchActionKeyword = keyword,
ActionKeyword.IndexSearchActionKeyword => IndexSearchActionKeyword = keyword,
ActionKeyword.QuickAccessActionKeyword => QuickAccessActionKeyword = keyword,
+ ActionKeyword.FolderSearchActionKeyword => FolderSearchActionKeyword = keyword,
+ ActionKeyword.FileSearchActionKeyword => FileSearchActionKeyword = keyword,
_ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyWord property not found")
};
@@ -190,6 +206,8 @@ internal enum ActionKeyword
ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled,
ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled,
ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled,
+ ActionKeyword.FolderSearchActionKeyword => FolderSearchKeywordEnabled,
+ ActionKeyword.FileSearchActionKeyword => FileSearchKeywordEnabled,
_ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined")
};
@@ -200,7 +218,27 @@ internal enum ActionKeyword
ActionKeyword.IndexSearchActionKeyword => IndexSearchKeywordEnabled = enable,
ActionKeyword.FileContentSearchActionKeyword => FileContentSearchKeywordEnabled = enable,
ActionKeyword.QuickAccessActionKeyword => QuickAccessKeywordEnabled = enable,
+ ActionKeyword.FolderSearchActionKeyword => FolderSearchKeywordEnabled = enable,
+ ActionKeyword.FileSearchActionKeyword => FileSearchKeywordEnabled = enable,
_ => throw new ArgumentOutOfRangeException(nameof(actionKeyword), actionKeyword, "ActionKeyword enabled status not defined")
};
+
+ // Returns a dictionary because some ActionKeywords may use wildcards (*),
+ // which means multiple ActionKeywords can be considered active at the same time.
+ // Using a dictionary ensures O(1) lookup time when checking which actions
+ // are enabled.
+ internal Dictionary GetActiveActionKeywords(string actionKeywordStr)
+ {
+ var result = new Dictionary();
+ if (string.IsNullOrEmpty(actionKeywordStr)) return null;
+ foreach (var action in Enum.GetValues())
+ {
+ var keywordStr = GetActionKeyword(action);
+ if (string.IsNullOrEmpty(keywordStr)) continue;
+ var isEnabled = GetActionKeywordEnabled(action);
+ if (keywordStr == actionKeywordStr && isEnabled) result.Add(action, keywordStr);
+ }
+ return result;
+ }
}
}
diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
index 2d46c6307cc..956c84db2c6 100644
--- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
+++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs
@@ -279,7 +279,11 @@ private void InitializeActionKeywordModels()
new(Settings.ActionKeyword.IndexSearchActionKeyword,
"plugin_explorer_actionkeywordview_indexsearch"),
new(Settings.ActionKeyword.QuickAccessActionKeyword,
- "plugin_explorer_actionkeywordview_quickaccess")
+ "plugin_explorer_actionkeywordview_quickaccess"),
+ new(Settings.ActionKeyword.FolderSearchActionKeyword,
+ "plugin_explorer_actionkeywordview_foldersearch"),
+ new(Settings.ActionKeyword.FileSearchActionKeyword,
+ "plugin_explorer_actionkeywordview_filesearch")
};
}