added keybind to open the renaming dialog

This commit is contained in:
Koisu 2025-06-23 15:23:57 -07:00
parent c943982684
commit 502a50b839
13 changed files with 173 additions and 45 deletions

View file

@ -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;
}

View file

@ -226,8 +226,25 @@ namespace Flow.Launcher.Plugin
/// <param name="query">Query string</param>
/// <param name="stringToCompare">The string that will be compared against the query</param>
/// <returns>Match results</returns>
MatchResult FuzzySearch(string query, string stringToCompare);
/// <summary>
/// Returns if the given name is valid file name even if it doesn't exist
/// </summary>
/// <param name="name">the name to check</param>
public bool IsValidFileName(string name);
/// <summary>
/// Returns if the given name is valid directory name even if it doesn't exist
/// </summary>
/// <param name="name">the name to check</param>
public bool IsValidDirectoryName(string name);
/// <summary>
/// Open A dialog to rename the given file or folder
/// </summary>
/// <param name="info">The directory or file info for the thing to rename. You must check if it actually exists or this will throw an exception</param>
/// <summary>
/// Http download the spefic url and return as string
/// </summary>

View file

@ -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<string> KeysToDisplay { get; set; } = new();
public ObservableCollection<string> KeysToDisplay { get; set; } = new ObservableCollection<string>();
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)

View file

@ -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)

View file

@ -555,4 +555,7 @@
<system:String x:Key="FileSize">File Size</system:String>
<system:String x:Key="Created">Created</system:String>
<system:String x:Key="LastModified">Last Modified</system:String>
<system:String x:Key="RenameFileHotkey">dcdc</system:String>
</ResourceDictionary>

View file

@ -211,6 +211,10 @@
Key="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding ForwardHistoryCommand}"
Modifiers="{Binding CycleHistoryDownHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
<KeyBinding
Key="{Binding RenameFileHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
Command="{Binding RenameFileCommand}"
Modifiers="{Binding RenameFileHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
</Window.InputBindings>
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">

View file

@ -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<char> invalidChars = Path.GetInvalidPathChars().ToList();
invalidChars.Add('/');
invalidChars.Add('\\');
if (name.IndexOfAny(invalidChars.ToArray()) >= 0 || name.Trim() == "")
{
return false;
}
return true;
}
private static async Task<Exception> RetryActionOnSTAThreadAsync(Action action, int retryCount = 6, int retryDelay = 150)
{
for (var i = 0; i < retryCount; i++)

View file

@ -204,6 +204,15 @@
<cc:HotkeyDisplay Margin="4 0 0 0" Keys="Ctrl+Minus" />
</StackPanel>
</cc:Card>
<cc:Card
Title="{DynamicResource RenameFileHotkey}"
Icon="&#xf0ae;"
Type="Inside">
<flowlauncher:HotkeyControl
DefaultHotkey=""
Type="RenameFileHotkey"
ValidateKeyGesture="True"/>
</cc:Card>
</StackPanel>
</cc:ExCard>

View file

@ -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)
{

View file

@ -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<char> 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
}
}

View file

@ -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<List<Result>> 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
}
}
}
}
}

View file

@ -47,16 +47,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search
internal async Task<List<Result>> SearchAsync(Query query, CancellationToken token)
{
var results = new HashSet<Result>(PathEqualityComparator.Instance);
if (ActionKeywordMatch(query, Settings.ActionKeyword.RenameActionKeyword))
{
return new List<Result>()
{
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")
};
}

View file

@ -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;
}
}
}
}