diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 9ec95215556..e9434be585a 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -39,8 +39,8 @@ public void GivenWindowsIndexSearch_WhenProvidedFolderPath_ThenQueryWhereRestric } [SupportedOSPlatform("windows7.0")] - [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}")] + [TestCase("C:\\", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND directory='file:C:\\' ORDER BY {QueryConstructor.OrderIdentifier}")] + [TestCase("C:\\SomeFolder\\", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND directory='file:C:\\SomeFolder\\' ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_ThenQueryShouldUseExpectedString(string folderPath, string expectedString) { // Given @@ -56,8 +56,7 @@ public void GivenWindowsIndexSearch_WhenSearchTypeIsTopLevelDirectorySearch_Then } [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'" + + [TestCase("C:\\SomeFolder", "flow.launcher.sln", "SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND directory='file:C:\\SomeFolder'" + " AND (System.FileName LIKE 'flow.launcher.sln%' OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"'))" + $" ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearchTopLevelDirectory_WhenSearchingForSpecificItem_ThenQueryShouldUseExpectedString( @@ -87,7 +86,7 @@ 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 {QueryConstructor.OrderIdentifier}")] + $"OR CONTAINS(System.FileName,'\"flow.launcher.sln*\"',1033) RANK BY COERCION(ABSOLUTE, 1000)) 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) @@ -104,7 +103,8 @@ public void GivenWindowsIndexSearch_WhenSearchAllFoldersAndFiles_ThenQueryShould var resultString = queryConstructor.FilesAndFolders(userSearchString); // Then - ClassicAssert.AreEqual(expectedString, resultString); + ClassicAssert.AreEqual(expectedString, resultString, $"Expected string: {expectedString}{Environment.NewLine} " + + $"Actual string was: {resultString}{Environment.NewLine}"); } [SupportedOSPlatform("windows7.0")] @@ -125,8 +125,7 @@ public void GivenWindowsIndexSearch_WhenQueryWhereRestrictionsIsForFileContentSe } [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 {QueryConstructor.OrderIdentifier}")] + [TestCase("some words", $"SELECT TOP 100 \"System.FileName\", \"System.ItemUrl\", \"System.ItemType\" FROM \"SystemIndex\" WHERE WorkId IS NOT NULL AND FREETEXT('some words') AND scope='file:' ORDER BY {QueryConstructor.OrderIdentifier}")] public void GivenWindowsIndexSearch_WhenSearchForFileContent_ThenQueryShouldUseExpectedString( string userSearchString, string expectedString) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs index 4bddfd9b27d..7e2a8e525f2 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Constants.cs @@ -36,6 +36,8 @@ internal static class Constants internal const string WindowsIndexingOptions = "srchadmin.dll"; + internal const string ExcludedFileTypesSeparator = ","; + internal static string ExplorerIconImageFullPath => Directory.GetParent(Assembly.GetExecutingAssembly().Location.ToString()) + "\\" + ExplorerIconImagePath; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index eb994a6f98f..602be877a1e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -72,14 +73,16 @@ private async ValueTask ClickToInstallEverythingAsync(ActionContext _) } } - public async IAsyncEnumerable SearchAsync(string search, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SearchAsync(string search, [EnumeratorCancellation] CancellationToken token, IEnumerable allowedResultTypes = null) { await ThrowIfEverythingNotAvailableAsync(token); if (token.IsCancellationRequested) yield break; - var option = new EverythingSearchOption(search, + var searchKeyword = BuildSearchKeyword(search, allowedResultTypes); + + var option = new EverythingSearchOption(searchKeyword, Settings.SortOption, MaxCount: Settings.MaxResult, IsFullPathSearch: Settings.EverythingSearchFullPath, @@ -89,6 +92,61 @@ public async IAsyncEnumerable SearchAsync(string search, [Enumerat yield return result; } + private string BuildSearchKeyword(string search, IEnumerable allowedResultTypes) + { + var filters = new List(); + + var typeFilter = BuildTypeFilter(allowedResultTypes); + if (!string.IsNullOrEmpty(typeFilter)) + filters.Add(typeFilter); + + var extensionFilter = BuildExtensionExclusionFilter(); + if (!string.IsNullOrEmpty(extensionFilter)) + filters.Add(extensionFilter); + + if (filters.Count == 0) + return search; + + var combinedFilters = string.Join(" ", filters); + return string.IsNullOrEmpty(search) ? combinedFilters : $"{combinedFilters} {search}"; + } + + private static string BuildTypeFilter(IEnumerable allowedResultTypes) + { + if (allowedResultTypes == null) + return ""; + + var hasFile = allowedResultTypes.Contains(ResultType.File); + var hasFolder = allowedResultTypes.Contains(ResultType.Folder); + var hasVolume = allowedResultTypes.Contains(ResultType.Volume); + + return (hasFile, hasFolder, hasVolume) switch + { + (true, false, false) => "file:", + (false, true, false) => "folder:", + (false, false, true) => "volume:", + (true, true, false) => "", + (true, false, true) => "", + (false, true, true) => "", + _ => "" // No filtering needed when all allowed or unspecified + }; + } + + private string BuildExtensionExclusionFilter() + { + // Split extensions, remove whitespace, and add dot prefix + var extensions = Settings.ExcludedFileTypeList + .Where(ext => !string.IsNullOrWhiteSpace(ext)) + .Select(ext => $"!*.{ext}") + .ToArray(); + + if (extensions.Length == 0) + return ""; + + // Everything syntax: !*.ext1 !*.ext2 to exclude these extensions + return string.Join(" ", extensions); + } + public async IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, [EnumeratorCancellation] CancellationToken token) { @@ -111,7 +169,10 @@ public async IAsyncEnumerable ContentSearchAsync(string plainSearc if (token.IsCancellationRequested) yield break; - var option = new EverythingSearchOption(plainSearch, + // Apply excluded file types in content search + var searchKeyword = BuildSearchKeyword(plainSearch, new[] { ResultType.File }); + + var option = new EverythingSearchOption(searchKeyword, Settings.SortOption, IsContentSearch: true, ContentSearchKeyword: contentSearch, @@ -132,7 +193,10 @@ public async IAsyncEnumerable EnumerateAsync(string path, string s if (token.IsCancellationRequested) yield break; - var option = new EverythingSearchOption(search, + // Apply excluded file types in path enumeration + var searchKeyword = BuildSearchKeyword(search, null); + + var option = new EverythingSearchOption(searchKeyword, Settings.SortOption, ParentPath: path, IsRecursive: recursive, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs index 9909b18d886..ae0f83621e0 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/IIndexProvider.cs @@ -3,8 +3,18 @@ namespace Flow.Launcher.Plugin.Explorer.Search.IProvider { + /// + /// Provides functionality for searching indexed items. + /// public interface IIndexProvider { - public IAsyncEnumerable SearchAsync(string search, CancellationToken token); + /// + /// Asynchronously searches for items matching the specified search criteria. + /// + /// The search query string. + /// The cancellation token to cancel the search operation. + /// Optional collection of result types to filter the search results. If null, all result types are included. + /// An asynchronous enumerable of objects matching the search criteria. + public IAsyncEnumerable SearchAsync(string search, CancellationToken token, IEnumerable allowedResultTypes = null); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 9420e3d3a31..871a08bad79 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -132,7 +132,7 @@ when activeActionKeywords.ContainsKey(ActionKeyword.QuickAccessActionKeyword): case false when CanUseIndexSearchByActionKeywords(activeActionKeywords): - searchResults = Settings.IndexProvider.SearchAsync(query.Search, token); + searchResults = Settings.IndexProvider.SearchAsync(query.Search, token, GetAllowedResultTypeByUsedActionKeyword(activeActionKeywords)); engineName = Enum.GetName(Settings.IndexSearchEngine); break; default: @@ -148,9 +148,12 @@ when CanUseIndexSearchByActionKeywords(activeActionKeywords): { await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false)) { + // TODO exclude in quick access if (search.Type == ResultType.File && IsExcludedFile(search)) continue; - + // TODO: Optimize filtering by action keyword at the provider level to reduce unnecessary searches. + // 3. Filter in quick access + // if (IsResultTypeFilteredByActionKeyword(search.Type, actions)) continue; @@ -195,6 +198,13 @@ private List EverythingContentSearchResult(Query query) ]; } + /// + /// Path search logic. Don't apply filtering by file extensions as it's like ls command. + /// + /// + /// + /// + /// private async Task> PathSearchAsync(Query query, CancellationToken token = default) { var querySearch = query.Search; @@ -291,7 +301,8 @@ private bool UseWindowsIndexForDirectorySearch(string locationPath) private bool IsExcludedFile(SearchResult result) { - string[] excludedFileTypes = Settings.ExcludedFileTypes.Split([','], StringSplitOptions.RemoveEmptyEntries); + // TODO may remove this function + string[] excludedFileTypes = Settings.ExcludedFileTypes.Split([Constants.ExcludedFileTypesSeparator], StringSplitOptions.RemoveEmptyEntries); string fileExtension = Path.GetExtension(result.FullPath).TrimStart('.'); return excludedFileTypes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase); @@ -329,6 +340,20 @@ private bool IsResultTypeFilteredByActionKeyword(ResultType type, List GetAllowedResultTypeByUsedActionKeyword(Dictionary activeActionKeywords) + { + List activeKeywords = activeActionKeywords.Keys.ToList(); + var allowedResultTypes = new List(); + foreach (var actionKeyword in activeKeywords) + { + if (_allowedTypesByActionKeyword.TryGetValue(actionKeyword, out var resultTypes)) + { + allowedResultTypes.AddRange(resultTypes); + } + } + return allowedResultTypes.Distinct().ToList(); + } + private bool CanUseIndexSearchByActionKeywords(Dictionary actions) { var keysToUseIndexSearch = new[] diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 1d160983a00..5837c81d7ee 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using Microsoft.Search.Interop; @@ -32,7 +34,7 @@ public CSearchQueryHelper CreateBaseQuery() baseQuery.QueryContentProperties = "System.FileName"; // Set sorting order - //baseQuery.QuerySorting = "System.ItemType DESC"; + baseQuery.QuerySorting = OrderIdentifier; return baseQuery; } @@ -60,21 +62,21 @@ internal static CSearchQueryHelper CreateQueryHelper() /// public string Directory(ReadOnlySpan path, ReadOnlySpan searchString = default, bool recursive = false) { - var queryConstraint = searchString.IsWhiteSpace() ? "" : $"AND (System.FileName LIKE '{searchString}%' OR CONTAINS(System.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 {OrderIdentifier}"; - - return query; + var baseQueryHelper = CreateBaseQuery(); + baseQueryHelper.QueryWhereRestrictions = $"AND {scopeConstraint}{queryConstraint}"; + return baseQueryHelper.GenerateSQLFromUserQuery("*"); } /// /// Search will be performed on all folders and files based on user's search keywords. /// - public string FilesAndFolders(ReadOnlySpan userSearchString) + public string FilesAndFolders(ReadOnlySpan userSearchString, IEnumerable allowedResultTypes = null) { if (userSearchString.IsWhiteSpace()) userSearchString = "*"; @@ -82,8 +84,69 @@ public string FilesAndFolders(ReadOnlySpan userSearchString) // Remove any special characters that might cause issues with the query var replacedSearchString = ReplaceSpecialCharacterWithTwoSideWhiteSpace(userSearchString); - // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause - return $"{CreateBaseQuery().GenerateSQLFromUserQuery(replacedSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}"; + var constraints = new List + { + RestrictionsForAllFilesAndFoldersSearch + }; + + var typeConstraint = BuildTypeFilterConstraint(allowedResultTypes); + if (!string.IsNullOrEmpty(typeConstraint)) + constraints.Add(typeConstraint); + + var extensionConstraint = BuildExtensionExclusionConstraint(); + if (!string.IsNullOrEmpty(extensionConstraint)) + constraints.Add(extensionConstraint); + + var queryHelper = CreateBaseQuery(); + queryHelper.QueryWhereRestrictions = $"AND {string.Join(" AND ", constraints)}"; + return queryHelper.GenerateSQLFromUserQuery(replacedSearchString); + } + + /// + /// Build WHERE clause constraint to filter by result types (File, Folder, Volume). + /// + /// + /// System.ItemType values: + /// - ".directory" for folders + /// - Specific file extensions (e.g., ".txt", ".pdf") for files + /// - Drive volumes are identified by their path structure + /// + private static string BuildTypeFilterConstraint(IEnumerable allowedResultTypes) + { + if (allowedResultTypes == null) + return null; + + var typesList = allowedResultTypes as IList ?? allowedResultTypes.ToList(); + var hasFile = typesList.Contains(ResultType.File); + var hasFolder = typesList.Contains(ResultType.Folder) || typesList.Contains(ResultType.Volume); // Folder and volume are merged in Folder action keyword so treat them as same + + // No filtering needed if empty or all types are allowed + if (hasFile && hasFolder || !hasFile && !hasFolder) + return null; + + if (hasFolder) + return "System.ItemType = '.directory'"; + + if (hasFile) + return "System.ItemType <> '.directory'"; + + return null; + } + + /// + /// Build WHERE clause constraint to exclude specific file extensions. + /// + /// Comma or semicolon separated file extensions without dots (e.g., "queryHelper,log,bak") + private string BuildExtensionExclusionConstraint() + { + var extensions = Settings.ExcludedFileTypeList + .Select(ext => $"System.FileExtension NOT LIKE '.{ext}'") + .ToArray(); + + if (extensions.Length == 0) + return ""; + + return string.Join(" AND ", extensions); } /// @@ -132,10 +195,19 @@ private static string ReplaceSpecialCharacterWithTwoSideWhiteSpace(ReadOnlySpan< /// public string FileContent(ReadOnlySpan userSearchString) { - string query = - $"SELECT TOP {Settings.MaxResult} {CreateBaseQuery().QuerySelectColumns} FROM {SystemIndex} WHERE {RestrictionsForFileContentSearch(userSearchString)} AND {RestrictionsForAllFilesAndFoldersSearch} ORDER BY {OrderIdentifier}"; + var constraints = new List + { + RestrictionsForFileContentSearch(userSearchString), + RestrictionsForAllFilesAndFoldersSearch + }; + + var extensionConstraint = BuildExtensionExclusionConstraint(); + if (!string.IsNullOrEmpty(extensionConstraint)) + constraints.Add(extensionConstraint); - return query; + var queryHelper = CreateBaseQuery(); + queryHelper.QueryWhereRestrictions = $"AND {string.Join(" AND ", constraints)}"; + return queryHelper.GenerateSQLFromUserQuery("*"); } /// diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index eeb5c2c4aaf..98207275eaa 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -45,13 +45,14 @@ private IAsyncEnumerable WindowsIndexFileContentSearchAsync( private IAsyncEnumerable WindowsIndexFilesAndFoldersSearchAsync( ReadOnlySpan querySearchString, + IEnumerable allowedResultTypes = null, CancellationToken token = default) { try { return WindowsIndex.WindowsIndexSearchAsync( QueryConstructor.CreateQueryHelper().ConnectionString, - QueryConstructor.FilesAndFolders(querySearchString), + QueryConstructor.FilesAndFolders(querySearchString, allowedResultTypes), token); } catch (COMException) @@ -83,9 +84,9 @@ private IAsyncEnumerable WindowsIndexTopLevelFolderSearchAsync( } } - public IAsyncEnumerable SearchAsync(string search, CancellationToken token) + public IAsyncEnumerable SearchAsync(string search, CancellationToken token, IEnumerable allowedResultTypes = null) { - return WindowsIndexFilesAndFoldersSearchAsync(search, token: token); + return WindowsIndexFilesAndFoldersSearchAsync(search, allowedResultTypes, token); } public IAsyncEnumerable ContentSearchAsync(string plainSearch, string contentSearch, CancellationToken token) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index e65c03e9be1..9c27d367cc9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; using System.Text.Json.Serialization; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.Everything; @@ -25,7 +26,37 @@ public class Settings public string ShellPath { get; set; } = "cmd"; - public string ExcludedFileTypes { get; set; } = ""; + [JsonIgnore] + private string _excludedFileTypes = ""; + /// + /// File extensions, without dot prefix separated by comma. + /// + public string ExcludedFileTypes + { + get => _excludedFileTypes; + set + { + if (_excludedFileTypes == value) return; + _excludedFileTypes = value; + _excludedFileTypeList = ExcludedFileTypes.Split(Constants.ExcludedFileTypesSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToArray(); + } + } + + [JsonIgnore] + private string[] _excludedFileTypeList = null; + + [JsonIgnore] + public string[] ExcludedFileTypeList + { + get + { + if (_excludedFileTypeList == null) + { + _excludedFileTypeList = ExcludedFileTypes.Split(Constants.ExcludedFileTypesSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToArray(); + } + return _excludedFileTypeList; + } + } public bool UseLocationAsWorkingDir { get; set; } = false; @@ -82,12 +113,16 @@ public class Settings public string PreviewPanelTimeFormat { get; set; } = "HH:mm"; - private EverythingSearchManager _everythingManagerInstance; - private WindowsIndexSearchManager _windowsIndexSearchManager; #region SearchEngine + [JsonIgnore] + private EverythingSearchManager _everythingManagerInstance; + [JsonIgnore] + private WindowsIndexSearchManager _windowsIndexSearchManager; + [JsonIgnore] private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this); + [JsonIgnore] private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 30c2c7f14b2..e59a909bd0f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -574,6 +574,8 @@ public string ExcludedFileTypes get => Settings.ExcludedFileTypes; set { + if (value == Settings.ExcludedFileTypes) + return; // remove spaces and dots from the string before saving string sanitized = string.IsNullOrEmpty(value) ? "" : value.Replace(" ", "").Replace(".", ""); Settings.ExcludedFileTypes = sanitized;