1- using Flow . Launcher . Plugin . Explorer . Search . DirectoryInfo ;
2- using Flow . Launcher . Plugin . Explorer . Search . Everything ;
3- using Flow . Launcher . Plugin . Explorer . Search . QuickAccessLinks ;
4- using Flow . Launcher . Plugin . SharedCommands ;
5- using System ;
1+ using System ;
62using System . Collections . Generic ;
73using System . Linq ;
84using System . Threading ;
95using System . Threading . Tasks ;
106using Flow . Launcher . Plugin . Explorer . Exceptions ;
7+ using Flow . Launcher . Plugin . Explorer . Search . DirectoryInfo ;
8+ using Flow . Launcher . Plugin . Explorer . Search . Everything ;
9+ using Flow . Launcher . Plugin . Explorer . Search . QuickAccessLinks ;
10+ using Flow . Launcher . Plugin . SharedCommands ;
11+ using static Flow . Launcher . Plugin . Explorer . Settings ;
1112using Path = System . IO . Path ;
1213
1314namespace Flow . Launcher . Plugin . Explorer . Search
@@ -18,6 +19,14 @@ public class SearchManager
1819
1920 internal Settings Settings ;
2021
22+ private readonly Dictionary < ActionKeyword , ResultType [ ] > _allowedTypesByActionKeyword = new ( )
23+ {
24+ { ActionKeyword . FileSearchActionKeyword , [ ResultType . File ] } ,
25+ { ActionKeyword . FolderSearchActionKeyword , [ ResultType . Folder , ResultType . Volume ] } ,
26+ { ActionKeyword . SearchActionKeyword , [ ResultType . File , ResultType . Folder , ResultType . Volume ] } ,
27+ } ;
28+
29+
2130 public SearchManager ( Settings settings , PluginInitContext context )
2231 {
2332 Context = context ;
@@ -44,86 +53,113 @@ public int GetHashCode(Result obj)
4453 }
4554 }
4655
56+ /// <summary>
57+ /// Results for the different types of searches as follows:
58+ /// 1. Search, only include results from:
59+ /// - Files
60+ /// - Folders
61+ /// - Quick Access Links
62+ /// - Path navigation
63+ /// 2. File Content Search, only include results from:
64+ /// - File contents from index search engines i.e. Windows Index, Everything (may not be available due its beta version)
65+ /// 3. Path Search, only include results from:
66+ /// - Path navigation
67+ /// 4. Quick Access Links, only include results from:
68+ /// - Full list of Quick Access Links if query is empty
69+ /// - Matched Quick Access Links if query is not empty
70+ /// - Quick Access Links that are matched on path, e.g. query "window" for results that contain 'window' in the path (even if not in the title),
71+ /// i.e. result with path c:\windows\system32
72+ /// 5. Folder Search, only include results from:
73+ /// - Folders
74+ /// - Quick Access Links
75+ /// 6. File Search, only include results from:
76+ /// - Files
77+ /// - Quick Access Links
78+ /// </summary>
4779 internal async Task < List < Result > > SearchAsync ( Query query , CancellationToken token )
4880 {
4981 var results = new HashSet < Result > ( PathEqualityComparator . Instance ) ;
5082
51- // This allows the user to type the below action keywords and see/search the list of quick folder links
52- if ( ActionKeywordMatch ( query , Settings . ActionKeyword . SearchActionKeyword )
53- || ActionKeywordMatch ( query , Settings . ActionKeyword . QuickAccessActionKeyword )
54- || ActionKeywordMatch ( query , Settings . ActionKeyword . PathSearchActionKeyword )
55- || ActionKeywordMatch ( query , Settings . ActionKeyword . IndexSearchActionKeyword )
56- || ActionKeywordMatch ( query , Settings . ActionKeyword . FileContentSearchActionKeyword ) )
83+ var keyword = query . ActionKeyword . Length == 0 ? Query . GlobalPluginWildcardSign : query . ActionKeyword ;
84+
85+ // No action keyword matched - plugin should not handle this query, return empty results.
86+ var activeActionKeywords = Settings . GetActiveActionKeywords ( keyword ) ;
87+ if ( activeActionKeywords . Count == 0 )
5788 {
58- if ( string . IsNullOrEmpty ( query . Search ) && ActionKeywordMatch ( query , Settings . ActionKeyword . QuickAccessActionKeyword ) )
59- return QuickAccess . AccessLinkListAll ( query , Settings . QuickAccessLinks ) ;
89+ return [ .. results ] ;
6090 }
61- else
91+
92+ var queryIsEmpty = string . IsNullOrEmpty ( query . Search ) ;
93+ if ( queryIsEmpty && activeActionKeywords . ContainsKey ( ActionKeyword . QuickAccessActionKeyword ) )
6294 {
63- // No action keyword matched- plugin should not handle this query, return empty results.
64- return new List < Result > ( ) ;
95+ return QuickAccess . AccessLinkListAll ( query , Settings . QuickAccessLinks ) ;
6596 }
6697
67- IAsyncEnumerable < SearchResult > searchResults ;
98+ if ( queryIsEmpty )
99+ {
100+ return [ .. results ] ;
101+ }
68102
69- bool isPathSearch = query . Search . IsLocationPathString ( )
103+ var isPathSearch = query . Search . IsLocationPathString ( )
70104 || EnvironmentVariables . IsEnvironmentVariableSearch ( query . Search )
71105 || EnvironmentVariables . HasEnvironmentVar ( query . Search ) ;
72106
107+ IAsyncEnumerable < SearchResult > searchResults ;
108+
73109 string engineName ;
74110
75111 switch ( isPathSearch )
76112 {
77113 case true
78- when ActionKeywordMatch ( query , Settings . ActionKeyword . PathSearchActionKeyword )
79- || ActionKeywordMatch ( query , Settings . ActionKeyword . SearchActionKeyword ) :
80-
114+ when CanUsePathSearchByActionKeywords ( activeActionKeywords ) :
81115 results . UnionWith ( await PathSearchAsync ( query , token ) . ConfigureAwait ( false ) ) ;
82-
83- return results . ToList ( ) ;
116+ return [ .. results ] ;
84117
85118 case false
86- when ActionKeywordMatch ( query , Settings . ActionKeyword . FileContentSearchActionKeyword ) :
87-
88119 // Intentionally require enabling of Everything's content search due to its slowness
120+ when activeActionKeywords . ContainsKey ( ActionKeyword . FileContentSearchActionKeyword ) :
89121 if ( Settings . ContentIndexProvider is EverythingSearchManager && ! Settings . EnableEverythingContentSearch )
90122 return EverythingContentSearchResult ( query ) ;
91123
92124 searchResults = Settings . ContentIndexProvider . ContentSearchAsync ( "" , query . Search , token ) ;
93125 engineName = Enum . GetName ( Settings . ContentSearchEngine ) ;
94126 break ;
95127
96- case false
97- when ActionKeywordMatch ( query , Settings . ActionKeyword . IndexSearchActionKeyword )
98- || ActionKeywordMatch ( query , Settings . ActionKeyword . SearchActionKeyword ) :
128+ case true or false
129+ when activeActionKeywords . ContainsKey ( ActionKeyword . QuickAccessActionKeyword ) :
130+ return QuickAccess . AccessLinkListMatched ( query , Settings . QuickAccessLinks ) ;
131+
99132
133+ case false
134+ when CanUseIndexSearchByActionKeywords ( activeActionKeywords ) :
100135 searchResults = Settings . IndexProvider . SearchAsync ( query . Search , token ) ;
101136 engineName = Enum . GetName ( Settings . IndexSearchEngine ) ;
102137 break ;
103-
104- case true or false
105- when ActionKeywordMatch ( query , Settings . ActionKeyword . QuickAccessActionKeyword ) :
106- return QuickAccess . AccessLinkListMatched ( query , Settings . QuickAccessLinks ) ;
107-
108138 default :
109- return results . ToList ( ) ;
139+ return [ .. results ] ;
140+
110141 }
111142
112- // Merge Quick Access Link results for non-path searches.
113- results . UnionWith ( QuickAccess . AccessLinkListMatched ( query , Settings . QuickAccessLinks ) ) ;
114143
144+ var actions = activeActionKeywords . Keys . ToList ( ) ;
145+ //Merge Quick Access Link results for non-path searches.
146+ results . UnionWith ( GetQuickAccessResultsFilteredByActionKeyword ( query , actions ) ) ;
115147 try
116148 {
117149 await foreach ( var search in searchResults . WithCancellation ( token ) . ConfigureAwait ( false ) )
118- if ( search . Type == ResultType . File && IsExcludedFile ( search ) ) {
150+ {
151+ if ( search . Type == ResultType . File && IsExcludedFile ( search ) )
119152 continue ;
120- } else {
121- results . Add ( ResultManager . CreateResult ( query , search ) ) ;
122- }
153+
154+ if ( IsResultTypeFilteredByActionKeyword ( search . Type , actions ) )
155+ continue ;
156+
157+ results . Add ( ResultManager . CreateResult ( query , search ) ) ;
158+ }
123159 }
124160 catch ( OperationCanceledException )
125161 {
126- return new List < Result > ( ) ;
162+ return [ .. results ] ;
127163 }
128164 catch ( EngineNotAvailableException )
129165 {
@@ -137,33 +173,13 @@ when ActionKeywordMatch(query, Settings.ActionKeyword.QuickAccessActionKeyword):
137173 results . RemoveWhere ( r => Settings . IndexSearchExcludedSubdirectoryPaths . Any (
138174 excludedPath => FilesFolders . PathContains ( excludedPath . Path , r . SubTitle , allowEqual : true ) ) ) ;
139175
140- return results . ToList ( ) ;
141- }
142-
143- private bool ActionKeywordMatch ( Query query , Settings . ActionKeyword allowedActionKeyword )
144- {
145- var keyword = query . ActionKeyword . Length == 0 ? Query . GlobalPluginWildcardSign : query . ActionKeyword ;
146-
147- return allowedActionKeyword switch
148- {
149- Settings . ActionKeyword . SearchActionKeyword => Settings . SearchActionKeywordEnabled &&
150- keyword == Settings . SearchActionKeyword ,
151- Settings . ActionKeyword . PathSearchActionKeyword => Settings . PathSearchKeywordEnabled &&
152- keyword == Settings . PathSearchActionKeyword ,
153- Settings . ActionKeyword . FileContentSearchActionKeyword => Settings . FileContentSearchKeywordEnabled &&
154- keyword == Settings . FileContentSearchActionKeyword ,
155- Settings . ActionKeyword . IndexSearchActionKeyword => Settings . IndexSearchKeywordEnabled &&
156- keyword == Settings . IndexSearchActionKeyword ,
157- Settings . ActionKeyword . QuickAccessActionKeyword => Settings . QuickAccessKeywordEnabled &&
158- keyword == Settings . QuickAccessActionKeyword ,
159- _ => throw new ArgumentOutOfRangeException ( nameof ( allowedActionKeyword ) , allowedActionKeyword , "actionKeyword out of range" )
160- } ;
176+ return [ .. results ] ;
161177 }
162178
163179 private List < Result > EverythingContentSearchResult ( Query query )
164180 {
165- return new List < Result > ( )
166- {
181+ return
182+ [
167183 new ( )
168184 {
169185 Title = Localize . flowlauncher_plugin_everything_enable_content_search ( ) ,
@@ -176,7 +192,7 @@ private List<Result> EverythingContentSearchResult(Query query)
176192 return false ;
177193 }
178194 }
179- } ;
195+ ] ;
180196 }
181197
182198 private async Task < List < Result > > PathSearchAsync ( Query query , CancellationToken token = default )
@@ -197,7 +213,7 @@ private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken
197213
198214 // Check that actual location exists, otherwise directory search will throw directory not found exception
199215 if ( ! FilesFolders . ReturnPreviousDirectoryIfIncompleteString ( path ) . LocationExists ( ) )
200- return results . ToList ( ) ;
216+ return [ .. results ] ;
201217
202218 var useIndexSearch = Settings . IndexSearchEngine is Settings . IndexSearchEngineOption . WindowsIndex
203219 && UseWindowsIndexForDirectorySearch ( path ) ;
@@ -209,7 +225,7 @@ private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken
209225 : ResultManager . CreateOpenCurrentFolderResult ( retrievedDirectoryPath , query . ActionKeyword , useIndexSearch ) ) ;
210226
211227 if ( token . IsCancellationRequested )
212- return new List < Result > ( ) ;
228+ return [ .. results ] ;
213229
214230 IAsyncEnumerable < SearchResult > directoryResult ;
215231
@@ -231,7 +247,7 @@ private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken
231247 }
232248
233249 if ( token . IsCancellationRequested )
234- return new List < Result > ( ) ;
250+ return [ .. results ] ;
235251
236252 try
237253 {
@@ -246,14 +262,14 @@ private async Task<List<Result>> PathSearchAsync(Query query, CancellationToken
246262 }
247263
248264
249- return results . ToList ( ) ;
265+ return [ .. results ] ;
250266 }
251267
252268 public bool IsFileContentSearch ( string actionKeyword ) => actionKeyword == Settings . FileContentSearchActionKeyword ;
253269
254270 public static bool UseIndexSearch ( string path )
255271 {
256- if ( Main . Settings . IndexSearchEngine is not Settings . IndexSearchEngineOption . WindowsIndex )
272+ if ( Main . Settings . IndexSearchEngine is not IndexSearchEngineOption . WindowsIndex )
257273 return false ;
258274
259275 // Check if the path is using windows index search
@@ -275,10 +291,67 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath)
275291
276292 private bool IsExcludedFile ( SearchResult result )
277293 {
278- string [ ] excludedFileTypes = Settings . ExcludedFileTypes . Split ( new [ ] { ',' } , StringSplitOptions . RemoveEmptyEntries ) ;
294+ string [ ] excludedFileTypes = Settings . ExcludedFileTypes . Split ( [ ',' ] , StringSplitOptions . RemoveEmptyEntries ) ;
279295 string fileExtension = Path . GetExtension ( result . FullPath ) . TrimStart ( '.' ) ;
280296
281297 return excludedFileTypes . Contains ( fileExtension , StringComparer . OrdinalIgnoreCase ) ;
282298 }
299+
300+ private List < Result > GetQuickAccessResultsFilteredByActionKeyword ( Query query , List < ActionKeyword > actions )
301+ {
302+ if ( ! Settings . QuickAccessKeywordEnabled )
303+ return [ ] ;
304+
305+ var results = QuickAccess . AccessLinkListMatched ( query , Settings . QuickAccessLinks ) ;
306+ if ( results . Count == 0 )
307+ return [ ] ;
308+
309+ return results
310+ . Where ( r => r . ContextData is SearchResult result
311+ && ! IsResultTypeFilteredByActionKeyword ( result . Type , actions ) )
312+ . ToList ( ) ;
313+ }
314+ private bool IsResultTypeFilteredByActionKeyword ( ResultType type , List < ActionKeyword > actions )
315+ {
316+ var actionsWithWhitelist = actions . Intersect ( _allowedTypesByActionKeyword . Keys ) . ToList ( ) ;
317+ if ( actionsWithWhitelist . Count == 0 ) return false ;
318+
319+ // Check if ANY active keyword allows this type (union behavior)
320+ foreach ( var action in actionsWithWhitelist )
321+ {
322+ if ( _allowedTypesByActionKeyword . TryGetValue ( action , out var allowedTypes ) )
323+ {
324+ if ( allowedTypes . Contains ( type ) )
325+ return false ;
326+ }
327+ }
328+
329+ return true ;
330+ }
331+
332+ private bool CanUseIndexSearchByActionKeywords ( Dictionary < ActionKeyword , string > actions )
333+ {
334+ var keysToUseIndexSearch = new [ ]
335+ {
336+ ActionKeyword . FileSearchActionKeyword , ActionKeyword . FolderSearchActionKeyword ,
337+ ActionKeyword . IndexSearchActionKeyword , ActionKeyword . SearchActionKeyword
338+ } ;
339+
340+ return keysToUseIndexSearch . Any ( actions . ContainsKey ) ;
341+ }
342+
343+ // Action keywords that supports patch search in results.
344+ private bool CanUsePathSearchByActionKeywords ( Dictionary < ActionKeyword , string > actions )
345+ {
346+ var keysThatSupportPathSearch = new [ ]
347+ {
348+ ActionKeyword . PathSearchActionKeyword ,
349+ ActionKeyword . SearchActionKeyword ,
350+ } ;
351+
352+ return keysThatSupportPathSearch . Any ( actions . ContainsKey ) ;
353+
354+ }
355+
283356 }
284357}
0 commit comments