diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index 4d2a4e50a..f6d144404 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -8,6 +8,7 @@ true false en + warnings diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/SortOptionTranlationHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/SortOptionTranlationHelper.cs new file mode 100644 index 000000000..d3a6552d9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/SortOptionTranlationHelper.cs @@ -0,0 +1,25 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using JetBrains.Annotations; +using System; + +namespace Flow.Launcher.Plugin.Explorer.Helper; + +public static class SortOptionTranslationHelper +{ + [CanBeNull] + public static IPublicAPI API { get; internal set; } + + public static string GetTranslatedName(this SortOption sortOption) + { + const string prefix = "flowlauncher_plugin_everything_sort_by_"; + + ArgumentNullException.ThrowIfNull(API); + + var enumName = Enum.GetName(sortOption); + var splited = enumName.Split('_'); + var name = string.Join('_', splited[..^1]); + var direction = splited[^1]; + + return $"{API.GetTranslation(prefix + name.ToLower())} {API.GetTranslation(prefix + direction.ToLower())}"; + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index bdd9dba17..691d61ffe 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -24,7 +24,12 @@ General Setting Customise Action Keywords Quick Access Links + Everything Setting + Sort Option: + Launch Hidden + Editor Path Index Search Excluded Paths + Use search result's location as executable working directory Use Index Search For Path Search Turning this on will return indexed directories/files faster, but if a directory/file is not indexed it will not show up. If a directory/file has been added to Index Search Excluded Path then it will still show up even if this option is on Indexing Options @@ -37,7 +42,11 @@ Done Enabled When disabled Flow will not execute this search option, and will additionally revert back to '*' to free up the action keyword - + Everything + Windows Index + Direct Enumeration + + Explorer Search and manage files and folders. Explorer utilises Windows Index Search diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 5c651266a..3b6096e20 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -1,4 +1,5 @@ using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Plugin.Explorer.Helper; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; @@ -16,7 +17,7 @@ namespace Flow.Launcher.Plugin.Explorer { public class Main : ISettingProvider, IAsyncPlugin, IContextMenu, IPluginI18n { - internal PluginInitContext Context { get; set; } + internal static PluginInitContext Context { get; set; } internal Settings Settings; @@ -50,6 +51,9 @@ namespace Flow.Launcher.Plugin.Explorer 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", Environment.Is64BitProcess ? "Everything64.dll" : "Everything86.dll")); return Task.CompletedTask; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs index 462a27d7f..82eecd8fd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/DirectoryInfoSearch.cs @@ -83,17 +83,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo { Log.Exception(nameof(DirectoryInfoSearch), "Error occured while searching path", e); - results.Add( - new Result - { - Title = string.Format(SearchManager.Context.API.GetTranslation( - "plugin_explorer_directoryinfosearch_error"), - e.Message), - Score = 501, - IcoPath = Constants.ExplorerIconImagePath - }); - - return results; + throw; } // Initial ordering, this order can be updated later by UpdateResultView.MainViewModel based on history of user selection. diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 99a5c35ba..255d52632 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -53,9 +53,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search if (string.IsNullOrEmpty(query.Search)) return QuickAccess.AccessLinkListAll(query, Settings.QuickAccessLinks); - var quickaccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks); + var quickAccessLinks = QuickAccess.AccessLinkListMatched(query, Settings.QuickAccessLinks); - results.UnionWith(quickaccessLinks); + results.UnionWith(quickAccessLinks); } IEnumerable searchResults; @@ -164,7 +164,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var recursiveIndicatorIndex = query.Search.IndexOf('>'); - if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathTraversalEngineOption.Direct) + if (recursiveIndicatorIndex > 0 && Settings.PathEnumerationEngine != Settings.PathEnumerationEngineOption.Direct) { directoryResult = await Settings.PathEnumerator.EnumerateAsync(query.Search[..recursiveIndicatorIndex], @@ -176,7 +176,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search } else { - directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); + try + { + directoryResult = DirectoryInfoSearch.TopLevelDirectorySearch(query, query.Search, token); + } + catch (Exception e) + { + results.Add( + new Result + { + Title = string.Format(SearchManager.Context.API.GetTranslation( + "plugin_explorer_directoryinfosearch_error"), + e.Message), + Score = 501, + IcoPath = Constants.ExplorerIconImagePath + }); + directoryResult = new List(); + } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs index 20e85bbb5..96fb01d7e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/QueryConstructor.cs @@ -104,6 +104,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex /// public string QueryForAllFilesAndFolders(string userSearchString) { + if (string.IsNullOrEmpty(userSearchString)) + userSearchString = "*"; + // Generate SQL from constructed parameters, converting the userSearchString from AQS->WHERE clause return CreateBaseQuery().GenerateSQLFromUserQuery(userSearchString) + " AND " + QueryWhereRestrictionsForAllFilesAndFoldersSearch + QueryOrderByFileNameRestriction; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 4e3ea2a12..33df7b562 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -6,6 +6,7 @@ using Flow.Launcher.Plugin.Explorer.Search.WindowsIndex; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using System.Text.Json.Serialization; @@ -15,14 +16,22 @@ namespace Flow.Launcher.Plugin.Explorer { public int MaxResult { get; set; } = 100; - public ObservableCollection QuickAccessLinks { get; set; } = new (); + public ObservableCollection QuickAccessLinks { get; set; } = new(); // as at v1.7.0 this is to maintain backwards compatibility, need to be removed afterwards. - public ObservableCollection QuickFolderAccessLinks { get; set; } = new (); + public ObservableCollection QuickFolderAccessLinks { get; set; } = new(); + + public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); + + public string EditorPath { get; set; } = ""; + + + public bool UseLocationAsWorkingDir { get; set; } = false; + + public bool ShowWindowsContextMenu { get; set; } = true; public bool UseWindowsIndexForDirectorySearch { get; set; } = false; - public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); public string SearchActionKeyword { get; set; } = Query.GlobalPluginWildcardSign; @@ -44,76 +53,86 @@ namespace Flow.Launcher.Plugin.Explorer public bool QuickAccessKeywordEnabled { get; set; } + public bool LaunchHidden { get; set; } = false; + + public bool WarnWindowsSearchServiceOff { get; set; } = true; - private IReadOnlyList _indexProviders; - private IReadOnlyList _fileContentIndexProviders; - private IReadOnlyList _pathEnumerables; - public Settings() - { - var everythingManager = new EverythingSearchManager(this); - var windowsIndexManager = new WindowsIndexSearchManager(this); - - _indexProviders = new List() - { - everythingManager, - windowsIndexManager - }; + private EverythingSearchManager _everythingManagerInstance; + private WindowsIndexSearchManager _windowsIndexSearchManager; + + #region SearchEngine + + public PathEnumerationEngineOption PathEnumerationEngine { get; set; } + + private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this); + private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); - _pathEnumerables = new List() - { - everythingManager, - windowsIndexManager - }; - _fileContentIndexProviders = new List - { - windowsIndexManager, - everythingManager, - }; - } - public IndexSearchEngineOption IndexSearchEngine { get; set; } [JsonIgnore] - public IIndexProvider IndexProvider => _indexProviders[(int)IndexSearchEngine]; - - public PathTraversalEngineOption PathEnumerationEngine { get; set; } + public IIndexProvider IndexProvider => IndexSearchEngine switch + { + IndexSearchEngineOption.Everything => EverythingManagerInstance, + IndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(IndexSearchEngine)) + }; + [JsonIgnore] - public IPathEnumerable PathEnumerator => _pathEnumerables[(int)PathEnumerationEngine]; + public IPathEnumerable PathEnumerator => PathEnumerationEngine switch + { + PathEnumerationEngineOption.Everything => EverythingManagerInstance, + PathEnumerationEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(PathEnumerationEngine)) + }; public ContentIndexSearchEngineOption ContentSearchEngine { get; set; } [JsonIgnore] - public IContentIndexProvider ContentIndexProvider => _fileContentIndexProviders[(int)ContentSearchEngine]; - - public enum PathTraversalEngineOption + public IContentIndexProvider ContentIndexProvider => ContentSearchEngine switch { + ContentIndexSearchEngineOption.Everything => EverythingManagerInstance, + ContentIndexSearchEngineOption.WindowsIndex => WindowsIndexSearchManager, + _ => throw new ArgumentOutOfRangeException(nameof(ContentSearchEngine)) + }; + + public enum PathEnumerationEngineOption + { + [Description("plugin_explorer_engine_everything")] Everything, + [Description("plugin_explorer_engine_windows_index")] WindowsIndex, + [Description("plugin_explorer_path_enumeration_engine_none")] Direct } public enum IndexSearchEngineOption { + [Description("plugin_explorer_engine_everything")] Everything, + [Description("plugin_explorer_engine_windows_index")] WindowsIndex } - + public enum ContentIndexSearchEngineOption { + [Description("plugin_explorer_engine_everything")] Everything, + [Description("plugin_explorer_engine_windows_index")] WindowsIndex } - - public bool LaunchHidden { get; set; } = false; + + #endregion + #region Everything Settings - + public string EverythingInstalledPath { get; set; } + [JsonIgnore] public SortOption[] SortOptions { get; set; } = Enum.GetValues(); public SortOption SortOption { get; set; } = SortOption.NAME_ASCENDING; - + public bool EnableEverythingContentSearch { get; set; } = false; #endregion diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/EnumBindingModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/EnumBindingModel.cs new file mode 100644 index 000000000..29c81a465 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/EnumBindingModel.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Flow.Launcher.Plugin.Explorer.ViewModels; + +public class EnumBindingModel where T : struct, Enum +{ + public static IReadOnlyList> CreateList() + { + return Enum.GetValues() + .Select(value => new EnumBindingModel + { + Value = value, LocalizationKey = GetDescriptionAttr(value) + }) + .ToArray(); + } + + public EnumBindingModel From(T value) + { + var name = value.ToString(); + var description = GetDescriptionAttr(value); + + return new EnumBindingModel + { + Name = name, + LocalizationKey = description, + Value = value + }; + } + + private static string GetDescriptionAttr(T source) + { + var fi = source.GetType().GetField(source.ToString()); + + var attributes = (DescriptionAttribute[])fi?.GetCustomAttributes( + typeof(DescriptionAttribute), false); + + return attributes is { Length: > 0 } ? attributes[0].Description : source.ToString(); + + } + + public string Name { get; set; } + private string LocalizationKey { get; set; } + public string Description => Main.Context.API.GetTranslation(LocalizationKey); + public T Value { get; set; } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index e88452c49..08f25cd51 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -1,27 +1,41 @@ using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin.Explorer.Search; +using Flow.Launcher.Plugin.Explorer.Search.Everything; +using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; using Flow.Launcher.Plugin.Explorer.Views; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading.Tasks; +using System.Windows; using System.Windows.Forms; using System.Windows.Input; +using MessageBox = System.Windows.Forms.MessageBox; namespace Flow.Launcher.Plugin.Explorer.ViewModels { - public class SettingsViewModel + public class SettingsViewModel : BaseModel { public Settings Settings { get; set; } internal PluginInitContext Context { get; set; } + public IReadOnlyList> IndexSearchEngines { get; set; } + public IReadOnlyList> ContentIndexSearchEngines { get; set; } + public IReadOnlyList> PathEnumerationEngines { get; set; } + public SettingsViewModel(PluginInitContext context, Settings settings) { Context = context; Settings = settings; + + InitializeEngineSelection(); + InitializeActionKeywordModels(); } @@ -30,17 +44,84 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Context.API.SaveSettingJsonStorage(); } + #region Engine Selection + + private EnumBindingModel _selectedIndexSearchEngine; + private EnumBindingModel _selectedContentSearchEngine; + private EnumBindingModel _selectedPathEnumerationEngine; + + + public EnumBindingModel SelectedIndexSearchEngine + { + get => _selectedIndexSearchEngine; + set + { + _selectedIndexSearchEngine = value; + Settings.IndexSearchEngine = value.Value; + OnPropertyChanged(); + } + } + + public EnumBindingModel SelectedContentSearchEngine + { + get => _selectedContentSearchEngine; + set + { + _selectedContentSearchEngine = value; + Settings.ContentSearchEngine = value.Value; + OnPropertyChanged(); + } + } + + public EnumBindingModel SelectedPathEnumerationEngine + { + get => _selectedPathEnumerationEngine; + set + { + _selectedPathEnumerationEngine = value; + Settings.PathEnumerationEngine = value.Value; + OnPropertyChanged(); + } + } + + private void InitializeEngineSelection() + { + IndexSearchEngines = EnumBindingModel.CreateList(); + ContentIndexSearchEngines = EnumBindingModel.CreateList(); + PathEnumerationEngines = EnumBindingModel.CreateList(); + + SelectedIndexSearchEngine = IndexSearchEngines.FirstOrDefault(x => x.Value == Settings.IndexSearchEngine); + _selectedContentSearchEngine = ContentIndexSearchEngines.FirstOrDefault(x => x.Value == Settings.ContentSearchEngine); + _selectedPathEnumerationEngine = PathEnumerationEngines.FirstOrDefault(x => x.Value == Settings.PathEnumerationEngine); + } + + #endregion + + + + #region ActionKeyword + + private void InitializeActionKeywordModels() + { + ActionKeywordsModels = new List + { + new(Settings.ActionKeyword.SearchActionKeyword, + Context.API.GetTranslation("plugin_explorer_actionkeywordview_search")), + new(Settings.ActionKeyword.FileContentSearchActionKeyword, + Context.API.GetTranslation("plugin_explorer_actionkeywordview_filecontentsearch")), + new(Settings.ActionKeyword.PathSearchActionKeyword, + Context.API.GetTranslation("plugin_explorer_actionkeywordview_pathsearch")), + new(Settings.ActionKeyword.IndexSearchActionKeyword, + Context.API.GetTranslation("plugin_explorer_actionkeywordview_indexsearch")), + new(Settings.ActionKeyword.QuickAccessActionKeyword, + Context.API.GetTranslation("plugin_explorer_actionkeywordview_quickaccess")) + }; + } + + public IReadOnlyList ActionKeywordsModels { get; set; } - public AccessLink SelectedQuickAccessLink { get; set; } - public AccessLink SelectedIndexSearchExcludedPath { get; set; } public ActionKeywordModel SelectedActionKeyword { get; set; } - - - public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink); - public ICommand EditLinkCommand => new RelayCommand(EditLink); - public ICommand AddLinkCommand => new RelayCommand(AddLink); - public ICommand EditActionKeywordCommand => new RelayCommand(EditActionKeyword); private void EditActionKeyword(object obj) @@ -53,130 +134,116 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API); - if (actionKeywordWindow.ShowDialog() ?? false) + if (!(actionKeywordWindow.ShowDialog() ?? false)) { - if (actionKeyword.Enabled && !actionKeywordWindow.KeywordEnabled) - { + return; + } + + switch (actionKeyword.Enabled, actionKeywordWindow.KeywordEnabled) + { + case (true, false): Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); - } - else if (!actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) - { - Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); - } - else if (actionKeyword.Enabled && actionKeywordWindow.KeywordEnabled) - { + break; + case (true, true): // same keyword will have dialog result false Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword); - } - - (actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled); + break; + case (false, true): + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); + break; + case (false, false): + throw new ArgumentException( + $"Both false in {nameof(actionKeyword)}.{nameof(actionKeyword.Enabled)} and {nameof(actionKeywordWindow)}.{nameof(actionKeywordWindow.KeywordEnabled)} should suggest that the ShowDialog() result is false"); } + (actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled); + } - private AccessLink? PromptUserSelectPath(ResultType type, string initialDirectory = null) + #endregion + + #region AccessLinks + + public AccessLink SelectedQuickAccessLink { get; set; } + public AccessLink SelectedIndexSearchExcludedPath { get; set; } + + + + public ICommand RemoveLinkCommand => new RelayCommand(RemoveLink); + public ICommand EditLinkCommand => new RelayCommand(EditLink); + public ICommand AddLinkCommand => new RelayCommand(AddLink); + + public void AppendLink(string containerName, AccessLink link) { - AccessLink newAccessLink = null; - - if (type is ResultType.Folder) + var container = containerName switch { - var folderBrowserDialog = new FolderBrowserDialog(); - - if (initialDirectory is not null) - folderBrowserDialog.InitialDirectory = initialDirectory; - - if (folderBrowserDialog.ShowDialog() != DialogResult.OK) - return newAccessLink; - - newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; - } - else if (type is ResultType.File) - { - var openFileDialog = new OpenFileDialog(); - if (initialDirectory is not null) - openFileDialog.InitialDirectory = initialDirectory; - - if (openFileDialog.ShowDialog() != DialogResult.OK) - return newAccessLink; - - newAccessLink = new AccessLink { Path = openFileDialog.FileName }; - } - return newAccessLink; + "QuickAccessLink" => Settings.QuickAccessLinks, + "IndexSearchExcludedPath" => Settings.IndexSearchExcludedSubdirectoryPaths, + _ => throw new ArgumentException($"Unknown container name: {containerName}") + }; + container.Add(link); } - private void EditLink(object obj) + private void EditLink(object commandParameter) { - if (obj is not string container) return; - - AccessLink selectedLink; - ObservableCollection collection; - - switch (container) + (AccessLink selectedLink, ObservableCollection collection) = commandParameter switch { - case "QuickAccessLink": - if (SelectedQuickAccessLink == null) - { - ShowUnselectedMessage(); - return; - } - selectedLink = SelectedQuickAccessLink; - collection = Settings.QuickAccessLinks; - break; - case "IndexSearchExcludedPaths": - if (SelectedIndexSearchExcludedPath == null) - { - ShowUnselectedMessage(); - return; - } - selectedLink = SelectedIndexSearchExcludedPath; - collection = Settings.IndexSearchExcludedSubdirectoryPaths; - break; - default: - return; + "QuickAccessLink" => (SelectedQuickAccessLink, Settings.QuickAccessLinks), + "IndexSearchExcludedPath" => (SelectedIndexSearchExcludedPath, Settings.IndexSearchExcludedSubdirectoryPaths), + _ => throw new ArgumentOutOfRangeException(nameof(commandParameter)) + }; + + if (selectedLink is null) + { + ShowUnselectedMessage(); + return; } - var link = PromptUserSelectPath(selectedLink.Type, + var path = PromptUserSelectPath(selectedLink.Type, selectedLink.Type == ResultType.Folder ? selectedLink.Path : Path.GetDirectoryName(selectedLink.Path)); - if (link is null) + if (path is null) return; collection.Remove(selectedLink); - collection.Add(link); + collection.Add(new AccessLink + { + Path = path, Type = selectedLink.Type, + }); } private void ShowUnselectedMessage() { - string warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); + var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); MessageBox.Show(warning); } - private void AddLink(object obj) + + private void AddLink(object commandParameter) { - if (obj is not string container) return; + var container = commandParameter switch + { + "QuickAccessLink" => Settings.QuickAccessLinks, + "IndexSearchExcludedPaths" => Settings.IndexSearchExcludedSubdirectoryPaths, + _ => throw new ArgumentOutOfRangeException(nameof(commandParameter)) + }; + + ArgumentNullException.ThrowIfNull(container); var folderBrowserDialog = new FolderBrowserDialog(); if (folderBrowserDialog.ShowDialog() != DialogResult.OK) return; - var newAccessLink = new AccessLink { Path = folderBrowserDialog.SelectedPath }; - - - switch (container) + var newAccessLink = new AccessLink { - case "QuickAccessLink": - if (SelectedQuickAccessLink == null) return; - Settings.QuickAccessLinks.Add(newAccessLink); - break; - case "IndexSearchExcludedPaths": - if (SelectedIndexSearchExcludedPath == null) return; - Settings.IndexSearchExcludedSubdirectoryPaths.Add(newAccessLink); - break; - } + Path = folderBrowserDialog.SelectedPath + }; + + container.Add(newAccessLink); } private void RemoveLink(object obj) @@ -197,11 +264,38 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Save(); } + #endregion + private string? PromptUserSelectPath(ResultType type, string initialDirectory = null) + { + string path = null; - internal void RemoveLinkFromQuickAccess(AccessLink selectedRow) => Settings.QuickAccessLinks.Remove(selectedRow); + if (type is ResultType.Folder) + { + var folderBrowserDialog = new FolderBrowserDialog(); + + if (initialDirectory is not null) + folderBrowserDialog.InitialDirectory = initialDirectory; + + if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + return path; + + path = folderBrowserDialog.SelectedPath; + } + else if (type is ResultType.File) + { + var openFileDialog = new OpenFileDialog(); + if (initialDirectory is not null) + openFileDialog.InitialDirectory = initialDirectory; + + if (openFileDialog.ShowDialog() != DialogResult.OK) + return path; + + path = openFileDialog.FileName; + } + return path; + } - internal void RemoveAccessLinkFromExcludedIndexPaths(AccessLink selectedRow) => Settings.IndexSearchExcludedSubdirectoryPaths.Remove(selectedRow); internal static void OpenWindowsIndexingOptions() { @@ -215,22 +309,69 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Process.Start(psi); } - internal void UpdateActionKeyword(Settings.ActionKeyword modifiedActionKeyword, string newActionKeyword, string oldActionKeyword) => - PluginManager.ReplaceActionKeyword(Context.CurrentPluginMetadata.ID, oldActionKeyword, newActionKeyword); + public bool UseWindowsIndexForDirectorySearch + { + get => Settings.UseWindowsIndexForDirectorySearch; + set => Settings.UseWindowsIndexForDirectorySearch = value; + } + public ICommand OpenEditorPath => new RelayCommand(_ => + { + var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null); + if (path is null) + return; - internal bool IsActionKeywordAlreadyAssigned(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); + EditorPath = path; + }); - internal bool IsNewActionKeywordGlobal(string newActionKeyword) => newActionKeyword == Query.GlobalPluginWildcardSign; - - public bool UseWindowsIndexForDirectorySearch { - get - { - return Settings.UseWindowsIndexForDirectorySearch; - } + public string EditorPath + { + get => Settings.EditorPath; set { - Settings.UseWindowsIndexForDirectorySearch = value; + Settings.EditorPath = value; + OnPropertyChanged(); } } + + + #region Everything FastSortWarning + + public Visibility FastSortWarningVisibility + { + get + { + try + { + return EverythingApi.IsFastSortOption(Settings.SortOption) ? Visibility.Collapsed : Visibility.Visible; + } + catch (IPCErrorException) + { + // this error occurs if the Everything service is not running, in this instance show the warning and + // update the message to let user know in the settings panel. + return Visibility.Visible; + } + } + } + public string SortOptionWarningMessage + { + get + { + try + { + // this method is used to determine if Everything service is running because as at Everything v1.4.1 + // the sdk does not provide a dedicated interface to determine if it is running. + return EverythingApi.IsFastSortOption(Settings.SortOption) ? string.Empty + : Context.API.GetTranslation("flowlauncher_plugin_everything_nonfastsort_warning"); + } + catch (IPCErrorException) + { + return Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"); + } + } + } + + #endregion + + } } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Converters/EverythingEnumNameConverter.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Converters/EverythingEnumNameConverter.cs new file mode 100644 index 000000000..e24b21dcd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Converters/EverythingEnumNameConverter.cs @@ -0,0 +1,20 @@ +using Flow.Launcher.Plugin.Everything.Everything; +using Flow.Launcher.Plugin.Explorer.Helper; +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Flow.Launcher.Plugin.Explorer.Views.Converters; + +public class EnumNameConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is SortOption option ? option.GetTranslatedName() : value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index fc37577f5..bbc25a597 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -7,6 +7,7 @@ xmlns:qa="clr-namespace:Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks" xmlns:viewModels="clr-namespace:Flow.Launcher.Plugin.Explorer.ViewModels" xmlns:views="clr-namespace:Flow.Launcher.Plugin.Explorer.Views" + xmlns:converters="clr-namespace:Flow.Launcher.Plugin.Explorer.Views.Converters" d:DataContext="{d:DesignInstance viewModels:SettingsViewModel}" d:DesignHeight="450" d:DesignWidth="800" @@ -15,7 +16,7 @@ - + + @@ -61,32 +63,145 @@ - + + + + + + - + + + + + +