From 9b471de3cf818f4026a20b4e5e7551e24e96f0da Mon Sep 17 00:00:00 2001 From: Hongtao Zhang Date: Sun, 15 May 2022 15:15:56 -0500 Subject: [PATCH] Implement Path Enumeration and ContentSearch --- Flow.Launcher/MainWindow.xaml.cs | 2 +- .../DirectoryInfo/DirectoryInfoSearch.cs | 33 ++++++------ .../Search/Everything/EverythingAPI.cs | 38 ++++++------- .../Everything/EverythingSearchManager.cs | 29 ++++++++-- .../Everything/EverythingSearchOption.cs | 35 ++++++++++++ .../Search/IContentIndexProvider.cs | 2 +- .../Search/SearchManager.cs | 54 +++++++++++++++---- .../WindowsIndex/WindowsIndexSearchManager.cs | 4 +- .../Flow.Launcher.Plugin.Explorer/Settings.cs | 5 +- 9 files changed, 147 insertions(+), 55 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 366407182..37f205288 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -520,7 +520,7 @@ namespace Flow.Launcher && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) { var queryWithoutActionKeyword = - QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins).Search; + QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 14c90d57f..27498002c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -10,7 +10,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { public static class DirectoryInfoSearch { - internal static List TopLevelDirectorySearch(Query query, string search, CancellationToken token) + internal static IEnumerable TopLevelDirectorySearch(Query query, string search, CancellationToken token) { var criteria = ConstructSearchCriteria(search); @@ -44,31 +44,36 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo return incompleteName; } - private static List DirectorySearch(EnumerationOptions enumerationOption, Query query, string search, + private static IEnumerable DirectorySearch(EnumerationOptions enumerationOption, Query query, string search, string searchCriteria, CancellationToken token) { - var results = new List(); + var results = new List(); var path = FilesFolders.ReturnPreviousDirectoryIfIncompleteString(search); - var folderList = new List(); - var fileList = new List(); - try { var directoryInfo = new System.IO.DirectoryInfo(path); - foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption) - ) + foreach (var fileSystemInfo in directoryInfo.EnumerateFileSystemInfos(searchCriteria, enumerationOption)) { if (fileSystemInfo is System.IO.DirectoryInfo) { - folderList.Add(ResultManager.CreateFolderResult(fileSystemInfo.Name, fileSystemInfo.FullName, - fileSystemInfo.FullName, query, 0, true, false)); + results.Add(new SearchResult() + { + FullPath = fileSystemInfo.FullName, + Type = ResultType.Folder, + WindowsIndexed = false + }); } else { - fileList.Add(ResultManager.CreateFileResult(fileSystemInfo.FullName, query, 0, true, false)); + results.Add(new SearchResult() + { + FullPath = fileSystemInfo.FullName, + Type = ResultType.File, + WindowsIndexed = false + }); } token.ThrowIfCancellationRequested(); @@ -77,13 +82,11 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo catch (Exception e) { Log.Exception("Flow.Plugin.Explorer.", nameof(DirectoryInfoSearch), e); - results.Add(new Result {Title = e.Message, Score = 501}); - - return results; + throw; } // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. - return results.Concat(folderList.OrderBy(x => x.Title)).Concat(fileList.OrderBy(x => x.Title)).ToList(); + return results.OrderBy(r=>r.Type).ThenBy(r=>r.FullPath); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs index 501ce5b22..92538d554 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingAPI.cs @@ -107,38 +107,38 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything /// The offset. /// The max count. /// - public static IEnumerable SearchAsync(string keyword, - CancellationToken token, - SortOption sortOption = SortOption.NAME_ASCENDING, - string parentPath = "", - bool recursive = false, - int offset = 0, - int maxCount = 100) + public static IEnumerable SearchAsync(EverythingSearchOption option, + CancellationToken token) { - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset)); + if (option.Offset < 0) + throw new ArgumentOutOfRangeException(nameof(option.Offset), option.Offset, "Offset must be greater than or equal to 0"); - if (maxCount < 0) - throw new ArgumentOutOfRangeException(nameof(maxCount)); + if (option.MaxCount < 0) + throw new ArgumentOutOfRangeException(nameof(option.MaxCount), option.MaxCount, "MaxCount must be greater than or equal to 0"); lock (syncObject) { - if (keyword.StartsWith("@")) + if (option.Keyword.StartsWith("@")) { EverythingApiDllImport.Everything_SetRegex(true); - keyword = keyword[1..]; + option.Keyword = option.Keyword[1..]; } - if (!string.IsNullOrEmpty(parentPath)) + if (!string.IsNullOrEmpty(option.ParentPath)) { - keyword += $" {(recursive ? "" : "parent:")}\"{parentPath}\""; + option.Keyword += $" {(option.IsRecursive ? "" : "parent:")}\"{option.ParentPath}\""; } - EverythingApiDllImport.Everything_SetSearchW(keyword); - EverythingApiDllImport.Everything_SetOffset(offset); - EverythingApiDllImport.Everything_SetMax(maxCount); + if (option.IsContentSearch) + { + option.Keyword += $" content:\"{option.ContentSearchKeyword}\""; + } - EverythingApiDllImport.Everything_SetSort(sortOption); + EverythingApiDllImport.Everything_SetSearchW(option.Keyword); + EverythingApiDllImport.Everything_SetOffset(option.Offset); + EverythingApiDllImport.Everything_SetMax(option.MaxCount); + + EverythingApiDllImport.Everything_SetSort(option.SortOption); if (token.IsCancellationRequested) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index 14069f678..3bd4a3c7c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -8,7 +8,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything public class EverythingSearchManager : IIndexProvider, IContentIndexProvider, IPathEnumerable { private Settings Settings { get; } - + public EverythingSearchManager(Settings settings) { Settings = settings; @@ -17,16 +17,35 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything public ValueTask> SearchAsync(string search, CancellationToken token) { - return ValueTask.FromResult(EverythingApi.SearchAsync(search, token, Settings.SortOption)); + return ValueTask.FromResult(EverythingApi.SearchAsync( + new EverythingSearchOption(search, Settings.SortOption), + token)); } - public ValueTask> ContentSearchAsync(string search, CancellationToken token) + public ValueTask> ContentSearchAsync(string plainSearch, + string contentSearch, CancellationToken token) { - return new ValueTask>(new List()); + if (!Settings.EnableEverythingContentSearch) + { + return new ValueTask>(new List()); + } + + return new ValueTask>(EverythingApi.SearchAsync( + new EverythingSearchOption( + plainSearch, + Settings.SortOption, + true, + contentSearch), + token)); } public ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { return new ValueTask>( - EverythingApi.SearchAsync("", token, Settings.SortOption, path, recursive)); + EverythingApi.SearchAsync( + new EverythingSearchOption(search, + Settings.SortOption, + parentPath: path, + isRecursive: recursive), + token)); } } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs new file mode 100644 index 000000000..ef41cf1f0 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchOption.cs @@ -0,0 +1,35 @@ +using Flow.Launcher.Plugin.Everything.Everything; + +namespace Flow.Launcher.Plugin.Explorer.Search.Everything +{ + public struct EverythingSearchOption + { + public EverythingSearchOption(string keyword, + SortOption sortOption, + bool isContentSearch = false, + string contentSearchKeyword = "", + string parentPath = "", + bool isRecursive = true, + int offset = 0, + int maxCount = 100) + { + Keyword = keyword; + SortOption = sortOption; + ContentSearchKeyword = contentSearchKeyword; + IsContentSearch = isContentSearch; + ParentPath = parentPath; + IsRecursive = isRecursive; + Offset = offset; + MaxCount = maxCount; + } + public string Keyword { get; set; } + public SortOption SortOption { get; set; } + public string ParentPath { get; set; } + public bool IsRecursive { get; set; } + + public bool IsContentSearch { get; set; } + public string ContentSearchKeyword { get; set; } + public int Offset { get;set; } + public int MaxCount { get; set; } + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs index b2142357e..bd7e14a66 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IContentIndexProvider.cs @@ -6,6 +6,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search { public interface IContentIndexProvider { - public ValueTask> ContentSearchAsync(string search, CancellationToken token); + public ValueTask> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token); } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 93707dcf2..99a5c35ba 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -1,4 +1,5 @@ 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.Explorer.Search.WindowsIndex; using Flow.Launcher.Plugin.SharedCommands; @@ -59,9 +60,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search IEnumerable searchResults; - if (IsFileContentSearch(query.ActionKeyword)) + if (ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword)) { - searchResults = await Settings.ContentIndexProvider.ContentSearchAsync(query.Search, token); + if (Settings.ContentIndexProvider is EverythingSearchManager && !Settings.EnableEverythingContentSearch) + { + return EverythingContentSearchResult(query); + } + searchResults = await Settings.ContentIndexProvider.ContentSearchAsync("", query.Search, token); } else { @@ -74,9 +79,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search results.UnionWith(await PathSearchAsync(query, token).ConfigureAwait(false)); } - if ((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) || - ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) && - querySearch.Length > 0 && + if (((ActionKeywordMatch(query, Settings.ActionKeyword.IndexSearchActionKeyword) || + ActionKeywordMatch(query, Settings.ActionKeyword.SearchActionKeyword)) && + querySearch.Length > 0) || + ActionKeywordMatch(query, Settings.ActionKeyword.FileContentSearchActionKeyword) && !querySearch.IsLocationPathString()) { if (searchResults != null) @@ -101,7 +107,27 @@ namespace Flow.Launcher.Plugin.Explorer.Search Settings.ActionKeyword.IndexSearchActionKeyword => Settings.IndexSearchKeywordEnabled && keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && - keyword == Settings.QuickAccessActionKeyword + keyword == Settings.QuickAccessActionKeyword, + _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range") + }; + } + + private static List EverythingContentSearchResult(Query query) + { + return new List() + { + new() + { + Title = "Do you want to enable content search for Everything?", + SubTitle = "It can be super slow without index (which is only supported in Everything 1.5+)", + IcoPath = "Images/search.png", + Action = c => + { + Settings.EnableEverythingContentSearch = true; + Context.API.ChangeQuery(query.RawQuery, true); + return false; + } + } }; } @@ -120,7 +146,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var isEnvironmentVariablePath = querySearch[1..].Contains("%\\"); var locationPath = querySearch; - + if (isEnvironmentVariablePath) locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath); @@ -136,18 +162,24 @@ namespace Flow.Launcher.Plugin.Explorer.Search IEnumerable directoryResult; - if (query.Search.Contains('>')) + var recursiveIndicatorIndex = query.Search.IndexOf('>'); + + if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathTraversalEngineOption.Direct) { directoryResult = - await Settings.PathEnumerator.EnumerateAsync(locationPath, "", false, token) + await Settings.PathEnumerator.EnumerateAsync(query.Search[..recursiveIndicatorIndex], + query.Search[(recursiveIndicatorIndex + 1)..], + true, + token) .ConfigureAwait(false); + } else { directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); } - - + + token.ThrowIfCancellationRequested(); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 1e64c54f6..05e75c646 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -59,9 +59,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex { return await WindowsIndexFilesAndFoldersSearchAsync(search, token); } - public async ValueTask> ContentSearchAsync(string search, CancellationToken token) + public async ValueTask> ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) { - return await WindowsIndexFileContentSearchAsync(search, token); + return await WindowsIndexFileContentSearchAsync(contentSearch, token); } public async ValueTask> EnumerateAsync(string path, string search, bool recursive, CancellationToken token) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index d9d2a788f..3c91eeabd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -67,7 +67,8 @@ namespace Flow.Launcher.Plugin.Explorer _fileContentIndexProviders = new List { - windowsIndexManager, everythingManager + windowsIndexManager, + everythingManager, }; } @@ -111,6 +112,8 @@ namespace Flow.Launcher.Plugin.Explorer public SortOption[] SortOptions { get; set; } = Enum.GetValues(); public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING; + + public bool EnableEverythingContentSearch { get; set; } = false; #endregion