diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 2dbdf0bf8..cd74178df 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -58,6 +58,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string OpenHistoryHotkey { get; set; } = $"Ctrl+H"; public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; + public string RenameFileHotkey { get; set; } = $"F2"; private string _language = Constant.SystemLanguageCode; public string Language @@ -472,13 +473,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); - + if (!string.IsNullOrEmpty(RenameFileHotkey)) + list.Add(new RegisteredHotkeyData(RenameFileHotkey, "RenameFileHotkey", () => RenameFileHotkey = "")); // Custom Query Hotkeys - foreach (var customPluginHotkey in CustomPluginHotkeys) - { - if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) - list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); - } + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } return list; } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f47ee5e11..9905d156d 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -226,8 +226,25 @@ namespace Flow.Launcher.Plugin /// Query string /// The string that will be compared against the query /// Match results + MatchResult FuzzySearch(string query, string stringToCompare); + /// + /// Returns if the given name is valid file name even if it doesn't exist + /// + /// the name to check + public bool IsValidFileName(string name); + /// + /// Returns if the given name is valid directory name even if it doesn't exist + /// + /// the name to check + public bool IsValidDirectoryName(string name); + /// + /// Open A dialog to rename the given file or folder + /// + /// The directory or file info for the thing to rename. You must check if it actually exists or this will throw an exception + + /// /// Http download the spefic url and return as string /// diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index e8961058c..073880343 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.IO; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; @@ -110,7 +111,9 @@ namespace Flow.Launcher SelectPrevItemHotkey, SelectPrevItemHotkey2, SelectNextItemHotkey, - SelectNextItemHotkey2 + SelectNextItemHotkey2, + RenameFileHotkey + } // We can initialize settings in static field because it has been constructed in App constuctor @@ -142,6 +145,8 @@ namespace Flow.Launcher HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2, HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey, HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2, + HotkeyType.RenameFileHotkey => _settings.RenameFileHotkey, + _ => throw new System.NotImplementedException("Hotkey type not set") }; } @@ -201,6 +206,9 @@ namespace Flow.Launcher case HotkeyType.SelectNextItemHotkey2: _settings.SelectNextItemHotkey2 = value; break; + case HotkeyType.RenameFileHotkey: + _settings.RenameFileHotkey = value; + break; default: throw new System.NotImplementedException("Hotkey type not set"); } @@ -231,7 +239,7 @@ namespace Flow.Launcher public string EmptyHotkey => App.API.GetTranslation("none"); - public ObservableCollection KeysToDisplay { get; set; } = new(); + public ObservableCollection KeysToDisplay { get; set; } = new ObservableCollection(); public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); @@ -308,6 +316,7 @@ namespace Flow.Launcher private void SetKeysToDisplay(HotkeyModel? hotkey) { + KeysToDisplay.Clear(); if (hotkey == null || hotkey == default(HotkeyModel)) @@ -318,8 +327,11 @@ namespace Flow.Launcher foreach (var key in hotkey.Value.EnumerateDisplayKeys()!) { + KeysToDisplay.Add(key); } + + } public void SetHotkey(string? keyStr, bool triggerValidate = true) diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index c7af8c5b8..9d6c8a4c3 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -135,10 +135,12 @@ public partial class HotkeyControlDialog : ContentDialog } if (tbMsg == null) + return; if (_hotkeySettings.RegisteredHotkeys.FirstOrDefault(v => v.Hotkey == hotkey) is { } registeredHotkeyData) { + var description = string.Format( App.API.GetTranslation(registeredHotkeyData.DescriptionResourceKey), registeredHotkeyData.DescriptionFormatVariables @@ -146,6 +148,7 @@ public partial class HotkeyControlDialog : ContentDialog Alert.Visibility = Visibility.Visible; if (registeredHotkeyData.RemoveHotkey is not null) { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableEditable"), description @@ -158,6 +161,7 @@ public partial class HotkeyControlDialog : ContentDialog } else { + tbMsg.Text = string.Format( App.API.GetTranslation("hotkeyUnavailableUneditable"), description @@ -175,6 +179,7 @@ public partial class HotkeyControlDialog : ContentDialog if (!CheckHotkeyAvailability(hotkey.Value, true)) { + tbMsg.Text = App.API.GetTranslation("hotkeyUnavailable"); Alert.Visibility = Visibility.Visible; SaveBtn.IsEnabled = false; @@ -182,10 +187,12 @@ public partial class HotkeyControlDialog : ContentDialog } else { + Alert.Visibility = Visibility.Collapsed; SaveBtn.IsEnabled = true; SaveBtn.Visibility = Visibility.Visible; } + } private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index bd4cbd282..29ab40076 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -555,4 +555,7 @@ File Size Created Last Modified + + + dcdc diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 9ff38a564..8ea2351de 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -211,6 +211,10 @@ Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}" Command="{Binding ForwardHistoryCommand}" Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> + diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 6e82032ff..382cfee0a 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -33,6 +33,7 @@ using Flow.Launcher.ViewModel; using JetBrains.Annotations; using Squirrel; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; +using Windows.Foundation.Metadata; namespace Flow.Launcher { @@ -222,7 +223,26 @@ namespace Flow.Launcher } } } - + public bool IsValidFileName(string name) + { + if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || name.Trim() == "") + { + return false; + } + return true; + } + public bool IsValidDirectoryName(string name) + { + List invalidChars = Path.GetInvalidPathChars().ToList(); + invalidChars.Add('/'); + invalidChars.Add('\\'); + if (name.IndexOfAny(invalidChars.ToArray()) >= 0 || name.Trim() == "") + { + return false; + } + return true; + } + private static async Task RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150) { for (var i = 0; i < retryCount; i++) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 89eb2dccd..4fb15492f 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -204,6 +204,15 @@ + + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 64a39fa62..6355b61a1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -22,6 +23,7 @@ using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; using Microsoft.VisualStudio.Threading; +using Svg; namespace Flow.Launcher.ViewModel { @@ -60,6 +62,8 @@ namespace Flow.Launcher.ViewModel #endregion + + #region Constructor public MainViewModel() @@ -140,6 +144,9 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.OpenHistoryHotkey): OnPropertyChanged(nameof(OpenHistoryHotkey)); break; + case nameof(Settings.RenameFileHotkey): + OnPropertyChanged(nameof(Settings.RenameFileHotkey)); + break; } }; @@ -596,6 +603,7 @@ namespace Flow.Launcher.ViewModel #endregion #region ViewModel Properties + public Settings Settings { get; } public string ClockText { get; private set; } @@ -913,9 +921,53 @@ namespace Flow.Launcher.ViewModel public string OpenHistoryHotkey => VerifyOrSetDefaultHotkey(Settings.OpenHistoryHotkey, "Ctrl+H"); public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); + public string RenameFileHotkey => VerifyOrSetDefaultHotkey(Settings.RenameFileHotkey, "F2"); + public bool StartWithEnglishMode => Settings.AlwaysStartEn; + #region renamingFiles + private int timesTriedToRenameFileWithExplorerDisabled = 0; + + [RelayCommand] + private void RenameFile() + { + const string explorerPluginID = "572be03c74c642baae319fc283e561a8"; + // check if explorer plugin is enabled + var explorerPluginMatches = App.API.GetAllPlugins().Where(plugin => plugin.Metadata.ID == "572be03c74c642baae319fc283e561a8"); + + if (!explorerPluginMatches.Any()) + { + timesTriedToRenameFileWithExplorerDisabled++; + return; + } + else if (!explorerPluginMatches.Any() && timesTriedToRenameFileWithExplorerDisabled > 3) + { + App.API.ShowMsg("Are you trying to rename a file?", "The explorer plugin needs to be enabled for the hotkey to rename files to work."); + timesTriedToRenameFileWithExplorerDisabled = 0; + } + else + { + dynamic explorerPlugin = explorerPluginMatches.First(); + string path = SelectedResults.SelectedItem.Result.SubTitle; + if (File.Exists(path) || Directory.Exists(path)) + { + explorerPlugin.Plugin.RenameDialog(new FileInfo(path), App.API ); // this feels kinda hacky + return; + } + else if (new DirectoryInfo(path).Parent == null) // check if isn't a root directory like C:\ + { + App.API.ShowMsgError("Cannot rename this."); + return; + } + + + + + } + } + #endregion + #endregion #region Preview @@ -1011,6 +1063,7 @@ namespace Flow.Launcher.ViewModel _ = ShowPreviewAsync(); } } + private async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs index a337b2dfb..e963b5b4b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/RenameThing.cs @@ -9,11 +9,11 @@ namespace Flow.Launcher.Plugin.Explorer.Helper { public static class RenameThing { - public static void Rename(this FileSystemInfo info, string newName) + public static void Rename(this FileSystemInfo info, string newName, IPublicAPI api) { if (info is FileInfo) { - if (newName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + if (!api.IsValidFileName(newName)) { throw new InvalidNameException(); } @@ -30,10 +30,10 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } else if (info is DirectoryInfo) { - List invalidChars = Path.GetInvalidPathChars().ToList(); - invalidChars.Add('/'); - invalidChars.Add('\\'); - if (newName.IndexOfAny(invalidChars.ToArray()) >= 0) + + + + if (!api.IsValidDirectoryName(newName)) { throw new InvalidNameException(); } @@ -47,12 +47,13 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } Directory.Move(info.FullName, Path.Join(parent.FullName, newName)); - } - else + }else { throw new ArgumentException($"{nameof(info)} must be either, {nameof(FileInfo)} or {nameof(DirectoryInfo)}"); } } + + } } internal class NotANewNameException : IOException @@ -85,5 +86,5 @@ namespace Flow.Launcher.Plugin.Explorer.Helper } -} + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 283820204..1225711fb 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Plugin.Explorer.Exceptions; +using System.Xml; +using System.CodeDom; namespace Flow.Launcher.Plugin.Explorer { @@ -42,11 +44,12 @@ 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 ? "x64" : "x86")); + return Task.CompletedTask; } @@ -55,8 +58,10 @@ namespace Flow.Launcher.Plugin.Explorer return contextMenu.LoadContextMenus(selectedResult); } + public async Task> QueryAsync(Query query, CancellationToken token) { + try { return await searchManager.SearchAsync(query, token); @@ -96,7 +101,10 @@ namespace Flow.Launcher.Plugin.Explorer { return Context.API.GetTranslation("plugin_explorer_plugin_description"); } - + public void RenameDialog(FileSystemInfo info, IPublicAPI api) + { + new RenameFile(api, info).Show(); + } private void FillQuickAccessLinkNames() { // Legacy version does not have names for quick access links, so we fill them with the path name. @@ -108,5 +116,6 @@ namespace Flow.Launcher.Plugin.Explorer } } } + } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index 28609c86d..12df6c145 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -47,16 +47,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal async Task> SearchAsync(Query query, CancellationToken token) { var results = new HashSet(PathEqualityComparator.Instance); - if (ActionKeywordMatch(query, Settings.ActionKeyword.RenameActionKeyword)) - { - return new List() - { - new Result(){ - Title = "Done", - AutoCompleteText = "test" - } - }; - } // 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) @@ -161,7 +151,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search keyword == Settings.IndexSearchActionKeyword, Settings.ActionKeyword.QuickAccessActionKeyword => Settings.QuickAccessKeywordEnabled && keyword == Settings.QuickAccessActionKeyword, - Settings.ActionKeyword.RenameActionKeyword => Settings.RenameActionKeywordEnabled && keyword == Settings.RenameActionKeyword, _ => throw new ArgumentOutOfRangeException(nameof(allowedActionKeyword), allowedActionKeyword, "actionKeyword out of range") }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs index 068015844..d27ef0e29 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/RenameFile.xaml.cs @@ -11,11 +11,11 @@ using Microsoft.VisualBasic.Logging; namespace Flow.Launcher.Plugin.Explorer.Views { - + [INotifyPropertyChanged] - public partial class RenameFile : Window + public partial class RenameFile : Window { - + public string NewFileName { @@ -28,7 +28,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views private string _newFileName; - + private readonly IPublicAPI _api; private readonly string _oldFilePath; @@ -47,27 +47,28 @@ namespace Flow.Launcher.Plugin.Explorer.Views RenameTb.SelectAll(); + _info = info; _oldFilePath = _info.FullName; NewFileName = _info.Name; - - + + } private void OnDoneButtonClick(object sender, RoutedEventArgs e) { // if it's just whitespace and nothing else - _api.LogInfo(nameof(RenameFile),$"THIS IS NEW FILE NAME: {NewFileName}"); + _api.LogInfo(nameof(RenameFile), $"THIS IS NEW FILE NAME: {NewFileName}"); if (NewFileName.Trim() == "" || NewFileName == "") { _api.ShowMsgError(string.Format(_api.GetTranslation("plugin_explorer_field_may_not_be_empty"), "New file name")); return; } - + try { - _info.Rename(NewFileName); + _info.Rename(NewFileName, _api); } catch (Exception exception) { @@ -100,14 +101,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views } } Close(); - + } private void BtnCancel(object sender, RoutedEventArgs e) { Close(); } - + private void RenameTb_OnKeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter) @@ -115,9 +116,10 @@ namespace Flow.Launcher.Plugin.Explorer.Views btnDone.Focus(); OnDoneButtonClick(sender, e); e.Handled = true; + } + + + } - - - } }