diff --git a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs index 6f3b23e11..841099dd1 100644 --- a/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs +++ b/Flow.Launcher.Core/ExternalPlugins/CommunityPluginSource.cs @@ -73,6 +73,17 @@ namespace Flow.Launcher.Core.ExternalPlugins return null; } } + catch (OperationCanceledException) when (token.IsCancellationRequested) + { + API.LogDebug(ClassName, $"Fetching from {ManifestFileUrl} was cancelled by caller."); + return null; + } + catch (TaskCanceledException) + { + // Likely an HttpClient timeout or external cancellation not requested by our token + API.LogWarn(ClassName, $"Fetching from {ManifestFileUrl} timed out."); + return null; + } catch (Exception e) { if (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 527950061..1369d7e5d 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -55,8 +55,8 @@ - - + + diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index 435d97ab7..9212dada6 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -27,6 +27,7 @@ namespace Flow.Launcher.Core.Plugin private JsonStorage> _storage = null!; + private static readonly double MainGridColumn0MaxWidthRatio = 0.6; private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin"); private static readonly Thickness SettingPanelItemLeftMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftMargin"); private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin"); @@ -156,7 +157,7 @@ namespace Flow.Launcher.Core.Plugin { if (!NeedCreateSettingPanel()) return null!; - // Create main grid with two columns (Column 1: Auto, Column 2: *) + // Create main grid with two columns (Column 0: Auto, Column 1: *) var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center }; mainPanel.ColumnDefinitions.Add(new ColumnDefinition() { @@ -200,7 +201,7 @@ namespace Flow.Launcher.Core.Plugin { Text = attributes.Label, VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.WrapWithOverflow + TextWrapping = TextWrapping.Wrap }; // Create a text block for description @@ -211,7 +212,7 @@ namespace Flow.Launcher.Core.Plugin { Text = attributes.Description, VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.WrapWithOverflow + TextWrapping = TextWrapping.Wrap }; desc.SetResourceReference(TextBlock.StyleProperty, "SettingPanelTextBlockDescriptionStyle"); // for theme change @@ -247,7 +248,8 @@ namespace Flow.Launcher.Core.Plugin VerticalAlignment = VerticalAlignment.Center, Margin = SettingPanelItemLeftTopBottomMargin, Text = Settings[attributes.Name] as string ?? string.Empty, - ToolTip = attributes.Description + ToolTip = attributes.Description, + TextWrapping = TextWrapping.Wrap }; textBox.TextChanged += (_, _) => @@ -269,7 +271,8 @@ namespace Flow.Launcher.Core.Plugin VerticalAlignment = VerticalAlignment.Center, Margin = SettingPanelItemLeftMargin, Text = Settings[attributes.Name] as string ?? string.Empty, - ToolTip = attributes.Description + ToolTip = attributes.Description, + TextWrapping = TextWrapping.Wrap }; textBox.TextChanged += (_, _) => @@ -333,7 +336,7 @@ namespace Flow.Launcher.Core.Plugin HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Center, Margin = SettingPanelItemLeftTopBottomMargin, - TextWrapping = TextWrapping.WrapWithOverflow, + TextWrapping = TextWrapping.Wrap, AcceptsReturn = true, Text = Settings[attributes.Name] as string ?? string.Empty, ToolTip = attributes.Description @@ -488,6 +491,8 @@ namespace Flow.Launcher.Core.Plugin rowCount++; } + mainPanel.SizeChanged += MainPanel_SizeChanged; + // Wrap the main grid in a user control return new UserControl() { @@ -495,6 +500,28 @@ namespace Flow.Launcher.Core.Plugin }; } + private void MainPanel_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (sender is not Grid grid) return; + + var workingWidth = grid.ActualWidth; + + if (workingWidth <= 0) return; + + var constrainedWidth = MainGridColumn0MaxWidthRatio * workingWidth; + + // Set MaxWidth of column 0 and its children + // We must set MaxWidth of its children to make text wrapping work correctly + grid.ColumnDefinitions[0].MaxWidth = constrainedWidth; + foreach (var child in grid.Children) + { + if (child is FrameworkElement element && Grid.GetColumn(element) == 0 && Grid.GetColumnSpan(element) == 1) + { + element.MaxWidth = constrainedWidth; + } + } + } + private static bool NeedSaveInSettings(string type) { return type != "textBlock" && type != "separator" && type != "hyperlink"; diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 4788efbf2..38eac13fb 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -14,7 +14,7 @@ using Flow.Launcher.Plugin; namespace Flow.Launcher.Core.Resource { - public class Internationalization + public class Internationalization : IDisposable { private static readonly string ClassName = nameof(Internationalization); @@ -30,6 +30,7 @@ namespace Flow.Launcher.Core.Resource private readonly List _languageDirectories = []; private readonly List _oldResources = []; private static string SystemLanguageCode; + private readonly SemaphoreSlim _langChangeLock = new(1, 1); public Internationalization(Settings settings) { @@ -185,20 +186,33 @@ namespace Flow.Launcher.Core.Resource private async Task ChangeLanguageAsync(Language language, bool updateMetadata = true) { - // Remove old language files and load language - RemoveOldLanguageFiles(); - if (language != AvailableLanguages.English) + await _langChangeLock.WaitAsync(); + + try { - LoadLanguage(language); + // Remove old language files and load language + RemoveOldLanguageFiles(); + if (language != AvailableLanguages.English) + { + LoadLanguage(language); + } + + // Change culture info + ChangeCultureInfo(language.LanguageCode); + + if (updateMetadata) + { + // Raise event for plugins after culture is set + await Task.Run(UpdatePluginMetadataTranslations); + } } - - // Change culture info - ChangeCultureInfo(language.LanguageCode); - - if (updateMetadata) + catch (Exception e) { - // Raise event for plugins after culture is set - await Task.Run(UpdatePluginMetadataTranslations); + API.LogException(ClassName, $"Failed to change language to <{language.LanguageCode}>", e); + } + finally + { + _langChangeLock.Release(); } } @@ -257,6 +271,7 @@ namespace Flow.Launcher.Core.Resource { dicts.Remove(r); } + _oldResources.Clear(); } private void LoadLanguage(Language language) @@ -384,5 +399,15 @@ namespace Flow.Launcher.Core.Resource } #endregion + + #region IDisposable + + public void Dispose() + { + RemoveOldLanguageFiles(); + _langChangeLock.Dispose(); + } + + #endregion } } diff --git a/Flow.Launcher.Core/packages.lock.json b/Flow.Launcher.Core/packages.lock.json index 5e9abc24c..b7a00d94d 100644 --- a/Flow.Launcher.Core/packages.lock.json +++ b/Flow.Launcher.Core/packages.lock.json @@ -13,15 +13,15 @@ }, "FSharp.Core": { "type": "Direct", - "requested": "[9.0.300, )", - "resolved": "9.0.300", - "contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ==" + "requested": "[9.0.303, )", + "resolved": "9.0.303", + "contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g==" }, "Meziantou.Framework.Win32.Jobs": { "type": "Direct", - "requested": "[3.4.3, )", - "resolved": "3.4.3", - "contentHash": "REjInKnQ0OrhjjtSMPQtLtdURctCroB4L8Sd2gjTOYDysklvsdnrStx1tHS7uLv+fSyFF3aazZmo5Ka0v1oz/w==" + "requested": "[3.4.4, )", + "resolved": "3.4.4", + "contentHash": "AivBzH5wM1NHBLehclim+o37SmireP7JxCRUoTilsc/h7LH9+YCPjb6Ig6y0khnQhFcO1P8RHYw4oiR15TGHUg==" }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Direct", @@ -90,8 +90,8 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2024.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "resolved": "2025.2.2", + "contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A==" }, "MemoryPack": { "type": "Transitive", @@ -199,21 +199,21 @@ }, "NLog": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug==" + "resolved": "6.0.4", + "contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw==" }, "NLog.OutputDebugString": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==", + "resolved": "6.0.4", + "contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==", "dependencies": { - "NLog": "6.0.1" + "NLog": "6.0.4" } }, "SharpVectors.Wpf": { "type": "Transitive", - "resolved": "1.8.4.2", - "contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng==" + "resolved": "1.8.5", + "contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg==" }, "Splat": { "type": "Transitive", @@ -254,14 +254,14 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", - "Flow.Launcher.Plugin": "[4.7.0, )", + "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", "Microsoft.VisualStudio.Threading": "[17.14.15, )", "NHotkey.Wpf": "[3.0.0, )", - "NLog": "[6.0.1, )", - "NLog.OutputDebugString": "[6.0.1, )", - "SharpVectors.Wpf": "[1.8.4.2, )", + "NLog": "[6.0.4, )", + "NLog.OutputDebugString": "[6.0.4, )", + "SharpVectors.Wpf": "[1.8.5, )", "System.Drawing.Common": "[7.0.0, )", "ToolGood.Words.Pinyin": "[3.1.0.3, )" } @@ -269,7 +269,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2024.3.0, )" + "JetBrains.Annotations": "[2025.2.2, )" } } } diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs index 1085cc833..6e2d86849 100644 --- a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs +++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Windows.Win32; namespace Flow.Launcher.Infrastructure { @@ -13,9 +9,10 @@ namespace Flow.Launcher.Infrastructure /// public static string GetActiveExplorerPath() { - var explorerWindow = GetActiveExplorer(); - string locationUrl = explorerWindow?.LocationURL; - return !string.IsNullOrEmpty(locationUrl) ? GetDirectoryPath(new Uri(locationUrl).LocalPath) : null; + var explorerPath = DialogJump.DialogJump.GetActiveExplorerPath(); + return !string.IsNullOrEmpty(explorerPath) ? + GetDirectoryPath(new Uri(explorerPath).LocalPath) : + null; } /// @@ -23,74 +20,12 @@ namespace Flow.Launcher.Infrastructure /// private static string GetDirectoryPath(string path) { - if (!path.EndsWith("\\")) + if (!path.EndsWith('\\')) { return path + "\\"; } return path; } - - /// - /// Gets the file explorer that is currently in the foreground - /// - private static dynamic GetActiveExplorer() - { - Type type = Type.GetTypeFromProgID("Shell.Application"); - if (type == null) return null; - dynamic shell = Activator.CreateInstance(type); - if (shell == null) - { - return null; - } - - var explorerWindows = new List(); - var openWindows = shell.Windows(); - for (int i = 0; i < openWindows.Count; i++) - { - var window = openWindows.Item(i); - if (window == null) continue; - - // find the desired window and make sure that it is indeed a file explorer - // we don't want the Internet Explorer or the classic control panel - // ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE" - if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe") - { - explorerWindows.Add(window); - } - } - - if (explorerWindows.Count == 0) return null; - - var zOrders = GetZOrder(explorerWindows); - - return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First; - } - - /// - /// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1. - /// - private static IEnumerable GetZOrder(List hWnds) - { - var z = new int[hWnds.Count]; - for (var i = 0; i < hWnds.Count; i++) z[i] = -1; - - var index = 0; - var numRemaining = hWnds.Count; - PInvoke.EnumWindows((wnd, _) => - { - var searchIndex = hWnds.FindIndex(x => new IntPtr(x.HWND) == wnd); - if (searchIndex != -1) - { - z[searchIndex] = index; - numRemaining--; - if (numRemaining == 0) return false; - } - index++; - return true; - }, IntPtr.Zero); - - return z; - } } } diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index c32c36248..5b4eaf893 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -56,24 +56,24 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all - + diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 64d323de6..598347fd2 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -22,7 +22,7 @@ namespace Flow.Launcher.Infrastructure.Image private static Lock storageLock { get; } = new(); private static BinaryStorage> _storage; private static readonly ConcurrentDictionary GuidToKey = new(); - private static IImageHashGenerator _hashGenerator; + private static ImageHashGenerator _hashGenerator; private static readonly bool EnableImageHash = true; public static ImageSource Image => ImageCache[Constant.ImageIcon, false]; public static ImageSource MissingImage => ImageCache[Constant.MissingImgIcon, false]; @@ -31,7 +31,7 @@ namespace Flow.Launcher.Infrastructure.Image public const int FullIconSize = 256; public const int FullImageSize = 320; - private static readonly string[] ImageExtensions = { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico" }; + private static readonly string[] ImageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".ico"]; private static readonly string SvgExtension = ".svg"; public static async Task InitializeAsync() @@ -327,7 +327,7 @@ namespace Flow.Launcher.Infrastructure.Image return img; } - private static ImageSource LoadFullImage(string path) + private static BitmapImage LoadFullImage(string path) { BitmapImage image = new BitmapImage(); image.BeginInit(); @@ -364,7 +364,7 @@ namespace Flow.Launcher.Infrastructure.Image return image; } - private static ImageSource LoadSvgImage(string path, bool loadFullImage = false) + private static RenderTargetBitmap LoadSvgImage(string path, bool loadFullImage = false) { // Set up drawing settings var desiredHeight = loadFullImage ? FullImageSize : SmallIconSize; diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 09eb98f46..2a5b826a9 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -34,7 +34,7 @@ namespace Flow.Launcher.Infrastructure.Logger var fileTarget = new FileTarget { - FileName = CurrentLogDirectory.Replace(@"\", "/") + "/${shortdate}.txt", + FileName = CurrentLogDirectory.Replace(@"\", "/") + "/Flow.Launcher.${date:format=yyyy-MM-dd}.log", Layout = layout }; @@ -65,26 +65,22 @@ namespace Flow.Launcher.Infrastructure.Logger public static void SetLogLevel(LOGLEVEL level) { - switch (level) + var rule = LogManager.Configuration.FindRuleByName("file"); + + var nlogLevel = level switch { - case LOGLEVEL.DEBUG: - UseDebugLogLevel(); - break; - default: - UseInfoLogLevel(); - break; - } - Info(nameof(Logger), $"Using log level: {level}."); - } + LOGLEVEL.NONE => LogLevel.Off, + LOGLEVEL.ERROR => LogLevel.Error, + LOGLEVEL.DEBUG => LogLevel.Debug, + _ => LogLevel.Info + }; - private static void UseDebugLogLevel() - { - LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Debug, LogLevel.Fatal); - } + rule.SetLoggingLevels(nlogLevel, LogLevel.Fatal); - private static void UseInfoLogLevel() - { - LogManager.Configuration.FindRuleByName("file").SetLoggingLevels(LogLevel.Info, LogLevel.Fatal); + LogManager.ReconfigExistingLoggers(); + + // We can't log Info when level is set to Error or None, so we use Debug + Debug(nameof(Logger), $"Using log level: {level}."); } private static void LogFaultyFormat(string message) @@ -169,7 +165,9 @@ namespace Flow.Launcher.Infrastructure.Logger public enum LOGLEVEL { - DEBUG, - INFO + NONE, + ERROR, + INFO, + DEBUG } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs index 24584115d..9c795f952 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomBrowserViewModel.cs @@ -1,11 +1,18 @@ +using System.Text.Json.Serialization; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Plugin; -using System.Text.Json.Serialization; namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomBrowserViewModel : BaseModel { + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + public string Name { get; set; } + [JsonIgnore] + public string DisplayName => Name == "Default" ? API.GetTranslation("defaultBrowser_default") : Name; public string Path { get; set; } public string PrivateArg { get; set; } public bool EnablePrivate { get; set; } @@ -26,8 +33,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings Editable = Editable }; } + + public void OnDisplayNameChanged() + { + OnPropertyChanged(nameof(DisplayName)); + } } } - - - diff --git a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs index c54c30478..2af0bb0e5 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/CustomExplorerViewModel.cs @@ -1,10 +1,18 @@ -using Flow.Launcher.Plugin; +using System.Text.Json.Serialization; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Plugin; -namespace Flow.Launcher.ViewModel +namespace Flow.Launcher.Infrastructure.UserSettings { public class CustomExplorerViewModel : BaseModel { + // We should not initialize API in static constructor because it will create another API instance + private static IPublicAPI api = null; + private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); + public string Name { get; set; } + [JsonIgnore] + public string DisplayName => Name == "Explorer" ? API.GetTranslation("fileManagerExplorer") : Name; public string Path { get; set; } public string FileArgument { get; set; } = "\"%d\""; public string DirectoryArgument { get; set; } = "\"%d\""; @@ -21,5 +29,10 @@ namespace Flow.Launcher.ViewModel Editable = Editable }; } + + public void OnDisplayNameChanged() + { + OnPropertyChanged(nameof(DisplayName)); + } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 0c3402050..f70c4559b 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text.Json.Serialization; using System.Windows; @@ -9,7 +9,6 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; -using Flow.Launcher.ViewModel; namespace Flow.Launcher.Infrastructure.UserSettings { diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 811733925..5d30b740d 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -904,5 +904,19 @@ namespace Flow.Launcher.Infrastructure } #endregion + + #region File / Folder Dialog + + public static string SelectFile() + { + var dlg = new OpenFileDialog(); + var result = dlg.ShowDialog(); + if (result == true) + return dlg.FileName; + + return string.Empty; + } + + #endregion } } diff --git a/Flow.Launcher.Infrastructure/packages.lock.json b/Flow.Launcher.Infrastructure/packages.lock.json index abd250f7c..47c94d5f6 100644 --- a/Flow.Launcher.Infrastructure/packages.lock.json +++ b/Flow.Launcher.Infrastructure/packages.lock.json @@ -25,9 +25,9 @@ }, "Fody": { "type": "Direct", - "requested": "[6.9.2, )", - "resolved": "6.9.2", - "contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w==" + "requested": "[6.9.3, )", + "resolved": "6.9.3", + "contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA==" }, "InputSimulator": { "type": "Direct", @@ -58,9 +58,9 @@ }, "Microsoft.Windows.CsWin32": { "type": "Direct", - "requested": "[0.3.183, )", - "resolved": "0.3.183", - "contentHash": "Ze3aE2y7xgzKxEWtNb4SH0CExXpCHr3sbmwnvMiWMzJhWDX/G4Rs5wgg2UNs3VN+qVHh/DkDWLCPaVQv/b//Nw==", + "requested": "[0.3.205, )", + "resolved": "0.3.205", + "contentHash": "U5wGAnyKd7/I2YMd43nogm81VMtjiKzZ9dsLMVI4eAB7jtv5IEj0gprj0q/F3iRmAIaGv5omOf8iSYx2+nE6BQ==", "dependencies": { "Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha", "Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview", @@ -78,17 +78,17 @@ }, "NLog": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug==" + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw==" }, "NLog.OutputDebugString": { "type": "Direct", - "requested": "[6.0.1, )", - "resolved": "6.0.1", - "contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==", "dependencies": { - "NLog": "6.0.1" + "NLog": "6.0.4" } }, "PropertyChanged.Fody": { @@ -102,9 +102,9 @@ }, "SharpVectors.Wpf": { "type": "Direct", - "requested": "[1.8.4.2, )", - "resolved": "1.8.4.2", - "contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng==" + "requested": "[1.8.5, )", + "resolved": "1.8.5", + "contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg==" }, "System.Drawing.Common": { "type": "Direct", @@ -123,8 +123,8 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2024.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "resolved": "2025.2.2", + "contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A==" }, "MemoryPack.Core": { "type": "Transitive", @@ -190,7 +190,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2024.3.0, )" + "JetBrains.Annotations": "[2025.2.2, )" } } } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index ae2454279..1ae0b1f58 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,4 +1,4 @@ - + net9.0-windows @@ -68,13 +68,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Plugin/packages.lock.json b/Flow.Launcher.Plugin/packages.lock.json index af835c598..70f71f20d 100644 --- a/Flow.Launcher.Plugin/packages.lock.json +++ b/Flow.Launcher.Plugin/packages.lock.json @@ -4,15 +4,15 @@ "net9.0-windows7.0": { "Fody": { "type": "Direct", - "requested": "[6.9.2, )", - "resolved": "6.9.2", - "contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w==" + "requested": "[6.9.3, )", + "resolved": "6.9.3", + "contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA==" }, "JetBrains.Annotations": { "type": "Direct", - "requested": "[2024.3.0, )", - "resolved": "2024.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "requested": "[2025.2.2, )", + "resolved": "2025.2.2", + "contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -26,9 +26,9 @@ }, "Microsoft.Windows.CsWin32": { "type": "Direct", - "requested": "[0.3.183, )", - "resolved": "0.3.183", - "contentHash": "Ze3aE2y7xgzKxEWtNb4SH0CExXpCHr3sbmwnvMiWMzJhWDX/G4Rs5wgg2UNs3VN+qVHh/DkDWLCPaVQv/b//Nw==", + "requested": "[0.3.205, )", + "resolved": "0.3.205", + "contentHash": "U5wGAnyKd7/I2YMd43nogm81VMtjiKzZ9dsLMVI4eAB7jtv5IEj0gprj0q/F3iRmAIaGv5omOf8iSYx2+nE6BQ==", "dependencies": { "Microsoft.Windows.SDK.Win32Docs": "0.1.42-alpha", "Microsoft.Windows.SDK.Win32Metadata": "61.0.15-preview", diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index 33479c5a0..11ccff05b 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -39,6 +39,7 @@ + @@ -49,8 +50,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Test/Plugins/CalculatorTest.cs b/Flow.Launcher.Test/Plugins/CalculatorTest.cs new file mode 100644 index 000000000..b075813db --- /dev/null +++ b/Flow.Launcher.Test/Plugins/CalculatorTest.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Flow.Launcher.Plugin.Calculator; +using Mages.Core; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace Flow.Launcher.Test.Plugins +{ + [TestFixture] + public class CalculatorPluginTest + { + private readonly Main _plugin; + private readonly Settings _settings = new() + { + DecimalSeparator = DecimalSeparator.UseSystemLocale, + MaxDecimalPlaces = 10, + ShowErrorMessage = false // Make sure we return the empty results when error occurs + }; + private readonly Engine _engine = new(new Configuration + { + Scope = new Dictionary + { + { "e", Math.E }, // e is not contained in the default mages engine + } + }); + + public CalculatorPluginTest() + { + _plugin = new Main(); + + var settingField = typeof(Main).GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance); + if (settingField == null) + Assert.Fail("Could not find field '_settings' on Flow.Launcher.Plugin.Calculator.Main"); + settingField.SetValue(_plugin, _settings); + + var engineField = typeof(Main).GetField("MagesEngine", BindingFlags.NonPublic | BindingFlags.Static); + if (engineField == null) + Assert.Fail("Could not find static field 'MagesEngine' on Flow.Launcher.Plugin.Calculator.Main"); + engineField.SetValue(null, _engine); + } + + // Basic operations + [TestCase(@"1+1", "2")] + [TestCase(@"2-1", "1")] + [TestCase(@"2*2", "4")] + [TestCase(@"4/2", "2")] + [TestCase(@"2^3", "8")] + // Decimal places + [TestCase(@"10/3", "3.3333333333")] + // Parentheses + [TestCase(@"(1+2)*3", "9")] + [TestCase(@"2^(1+2)", "8")] + // Functions + [TestCase(@"pow(2,3)", "8")] + [TestCase(@"min(1,-1,-2)", "-2")] + [TestCase(@"max(1,-1,-2)", "1")] + [TestCase(@"sqrt(16)", "4")] + [TestCase(@"sin(pi)", "0.0000000000")] + [TestCase(@"cos(0)", "1")] + [TestCase(@"tan(0)", "0")] + [TestCase(@"log10(100)", "2")] + [TestCase(@"log(100)", "2")] + [TestCase(@"log2(8)", "3")] + [TestCase(@"ln(e)", "1")] + [TestCase(@"abs(-5)", "5")] + // Constants + [TestCase(@"pi", "3.1415926536")] + // Complex expressions + [TestCase(@"(2+3)*sqrt(16)-log(100)/ln(e)", "18")] + [TestCase(@"sin(pi/2)+cos(0)+tan(0)", "2")] + // Error handling (should return empty result) + [TestCase(@"10/0", "")] + [TestCase(@"sqrt(-1)", "")] + [TestCase(@"log(0)", "")] + [TestCase(@"invalid_expression", "")] + public void CalculatorTest(string expression, string result) + { + ClassicAssert.AreEqual(GetCalculationResult(expression), result); + } + + private string GetCalculationResult(string expression) + { + var results = _plugin.Query(new Plugin.Query() + { + Search = expression + }); + return results.Count > 0 ? results[0].Title : string.Empty; + } + } +} diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 55d974c2e..480c78c66 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -45,6 +45,7 @@ namespace Flow.Launcher private static Settings _settings; private static MainWindow _mainWindow; private readonly MainViewModel _mainVM; + private readonly Internationalization _internationalization; // To prevent two disposals running at the same time. private static readonly object _disposingLock = new(); @@ -107,6 +108,7 @@ namespace Flow.Launcher API = Ioc.Default.GetRequiredService(); _settings.Initialize(); _mainVM = Ioc.Default.GetRequiredService(); + _internationalization = Ioc.Default.GetRequiredService(); } catch (Exception e) { @@ -195,7 +197,7 @@ namespace Flow.Launcher Win32Helper.EnableWin32DarkMode(_settings.ColorScheme); // Initialize language before portable clean up since it needs translations - await Ioc.Default.GetRequiredService().InitializeLanguageAsync(); + await _internationalization.InitializeLanguageAsync(); // Clean up after portability update Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); @@ -438,6 +440,7 @@ namespace Flow.Launcher _mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose); _mainVM?.Dispose(); DialogJump.Dispose(); + _internationalization.Dispose(); } API.LogInfo(ClassName, "End Flow Launcher dispose ----------------------------------------------------"); diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index fa23d8886..a99d4d8c2 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -40,49 +40,11 @@ - + - + @@ -132,7 +94,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -141,8 +103,8 @@ - - + + @@ -152,7 +114,7 @@ - + diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index 93b9a8aaa..fd04b3e88 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -16,7 +17,7 @@ public static class WallpaperPathRetrieval private const int MaxCacheSize = 3; private static readonly Dictionary<(string, DateTime), ImageBrush> WallpaperCache = new(); - private static readonly object CacheLock = new(); + private static readonly Lock CacheLock = new(); public static Brush GetWallpaperBrush() { @@ -31,7 +32,7 @@ public static class WallpaperPathRetrieval var wallpaperPath = Win32Helper.GetWallpaperPath(); if (string.IsNullOrEmpty(wallpaperPath) || !File.Exists(wallpaperPath)) { - App.API.LogInfo(ClassName, $"Wallpaper path is invalid: {wallpaperPath}"); + App.API.LogError(ClassName, $"Wallpaper path is invalid: {wallpaperPath}"); var wallpaperColor = GetWallpaperColor(); return new SolidColorBrush(wallpaperColor); } @@ -47,17 +48,22 @@ public static class WallpaperPathRetrieval return cachedWallpaper; } } - - using var fileStream = File.OpenRead(wallpaperPath); - var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None); - var frame = decoder.Frames[0]; - var originalWidth = frame.PixelWidth; - var originalHeight = frame.PixelHeight; + + int originalWidth, originalHeight; + // Use `using ()` instead of `using var` sentence here to ensure the wallpaper file is not locked + using (var fileStream = File.OpenRead(wallpaperPath)) + { + var decoder = BitmapDecoder.Create(fileStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None); + var frame = decoder.Frames[0]; + originalWidth = frame.PixelWidth; + originalHeight = frame.PixelHeight; + } if (originalWidth == 0 || originalHeight == 0) { - App.API.LogInfo(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}"); - return new SolidColorBrush(Colors.Transparent); + App.API.LogError(ClassName, $"Failed to load bitmap: Width={originalWidth}, Height={originalHeight}"); + var wallpaperColor = GetWallpaperColor(); + return new SolidColorBrush(wallpaperColor); } // Calculate the scaling factor to fit the image within 800x600 while preserving aspect ratio @@ -70,7 +76,9 @@ public static class WallpaperPathRetrieval // Set DecodePixelWidth and DecodePixelHeight to resize the image while preserving aspect ratio var bitmap = new BitmapImage(); bitmap.BeginInit(); + bitmap.CacheOption = BitmapCacheOption.OnLoad; // Use OnLoad to ensure the wallpaper file is not locked bitmap.UriSource = new Uri(wallpaperPath); + bitmap.CreateOptions = BitmapCreateOptions.IgnoreColorProfile; bitmap.DecodePixelWidth = decodedPixelWidth; bitmap.DecodePixelHeight = decodedPixelHeight; bitmap.EndInit(); @@ -104,13 +112,13 @@ public static class WallpaperPathRetrieval private static Color GetWallpaperColor() { - RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false); + using var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", false); var result = key?.GetValue("Background", null); if (result is string strResult) { try { - var parts = strResult.Trim().Split(new[] { ' ' }, 3).Select(byte.Parse).ToList(); + var parts = strResult.Trim().Split([' '], 3).Select(byte.Parse).ToList(); return Color.FromRgb(parts[0], parts[1], parts[2]); } catch (Exception ex) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index d2f78e1f6..626fe1385 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -219,6 +219,7 @@ Fail to uninstall {0} Unable to find plugin.json from the extracted zip file, or this path {0} does not exist A plugin with the same ID and version already exists, or the version is greater than this downloaded plugin + Error creating setting panel for plugin {0}:{1}{2} Plugin Store @@ -462,8 +463,10 @@ Open Folder Advanced Log Level - Debug + Silent + Error Info + Debug Setting Window Font @@ -485,6 +488,7 @@ Arg For File The file manager '{0}' could not be located at '{1}'. Would you like to continue? File Manager Path Error + File Explorer Default Web Browser @@ -495,6 +499,8 @@ New Window New Tab Private Mode + Default + New Profile Change Priority diff --git a/Flow.Launcher/SelectBrowserWindow.xaml b/Flow.Launcher/SelectBrowserWindow.xaml index d51d597b7..67c22b07d 100644 --- a/Flow.Launcher/SelectBrowserWindow.xaml +++ b/Flow.Launcher/SelectBrowserWindow.xaml @@ -92,7 +92,7 @@ SelectedIndex="{Binding SelectedCustomBrowserIndex}"> - + diff --git a/Flow.Launcher/SelectBrowserWindow.xaml.cs b/Flow.Launcher/SelectBrowserWindow.xaml.cs index 565b4cbc3..290712aad 100644 --- a/Flow.Launcher/SelectBrowserWindow.xaml.cs +++ b/Flow.Launcher/SelectBrowserWindow.xaml.cs @@ -1,6 +1,7 @@ using System.Windows; using System.Windows.Controls; using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Infrastructure; using Flow.Launcher.ViewModel; namespace Flow.Launcher @@ -31,7 +32,7 @@ namespace Flow.Launcher private void btnBrowseFile_Click(object sender, RoutedEventArgs e) { - var selectedFilePath = _viewModel.SelectFile(); + var selectedFilePath = Win32Helper.SelectFile(); if (!string.IsNullOrEmpty(selectedFilePath)) { diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml b/Flow.Launcher/SelectFileManagerWindow.xaml index b3b219d1c..cd4bec424 100644 --- a/Flow.Launcher/SelectFileManagerWindow.xaml +++ b/Flow.Launcher/SelectFileManagerWindow.xaml @@ -102,7 +102,7 @@ SelectedIndex="{Binding SelectedCustomExplorerIndex}"> - + diff --git a/Flow.Launcher/SelectFileManagerWindow.xaml.cs b/Flow.Launcher/SelectFileManagerWindow.xaml.cs index d9c672aff..5143f9a56 100644 --- a/Flow.Launcher/SelectFileManagerWindow.xaml.cs +++ b/Flow.Launcher/SelectFileManagerWindow.xaml.cs @@ -2,6 +2,7 @@ using System.Windows.Controls; using System.Windows.Navigation; using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Infrastructure; using Flow.Launcher.ViewModel; namespace Flow.Launcher @@ -32,13 +33,13 @@ namespace Flow.Launcher private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) { - _viewModel.OpenUrl(e.Uri.AbsoluteUri); + App.API.OpenUrl(e.Uri.AbsoluteUri); e.Handled = true; } private void btnBrowseFile_Click(object sender, RoutedEventArgs e) { - var selectedFilePath = _viewModel.SelectFile(); + var selectedFilePath = Win32Helper.SelectFile(); if (!string.IsNullOrEmpty(selectedFilePath)) { diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 1efc89972..647b36701 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -231,35 +231,41 @@ public partial class SettingsPaneAboutViewModel : BaseModel } }); - // Firstly, delete plugin cache directories - pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) - .ToList() - .ForEach(dir => + // Check if plugin cache directory exists before attempting to delete + // Or it will throw DirectoryNotFoundException in `pluginCacheDirectory.EnumerateDirectories` + if (pluginCacheDirectory.Exists) + { + // Firstly, delete plugin cache directories + pluginCacheDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) + .ToList() + .ForEach(dir => + { + try + { + // Plugin may create directories in its cache directory + dir.Delete(recursive: true); + } + catch (Exception e) + { + App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e); + success = false; + } + }); + + // Then, delete plugin directory + var dir = pluginCacheDirectory; + try { - try - { - // Plugin may create directories in its cache directory - dir.Delete(recursive: true); - } - catch (Exception e) - { - App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e); - success = false; - } - }); - - // Then, delete plugin directory - var dir = GetPluginCacheDir(); - try - { - dir.Delete(recursive: false); - } - catch (Exception e) - { - App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e); - success = false; + dir.Delete(recursive: false); + } + catch (Exception e) + { + App.API.LogException(ClassName, $"Failed to delete cache directory: {dir.Name}", e); + success = false; + } } + // Raise regardless to cover scenario where size needs to be recalculated if the folder is manually removed on disk. OnPropertyChanged(nameof(CacheFolderSize)); return success; diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index ec75ddf90..b47b53654 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; @@ -219,6 +219,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel DropdownDataGeneric.UpdateLabels(DialogJumpFileResultBehaviours); // Since we are using Binding instead of DynamicResource, we need to manually trigger the update OnPropertyChanged(nameof(AlwaysPreviewToolTip)); + Settings.CustomExplorer.OnDisplayNameChanged(); + Settings.CustomBrowser.OnDisplayNameChanged(); } public string Language diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index 81e15df69..07cc7b6a7 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -403,7 +403,7 @@ MaxWidth="250" Margin="10 0 0 0" Command="{Binding SelectFileManagerCommand}" - Content="{Binding Settings.CustomExplorer.Name}" /> + Content="{Binding Settings.CustomExplorer.DisplayName}" /> + Content="{Binding Settings.CustomBrowser.DisplayName}" /> diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 41181f58d..bcc502f44 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -14,8 +15,13 @@ namespace Flow.Launcher.ViewModel { public partial class PluginViewModel : BaseModel { + private static readonly string ClassName = nameof(PluginViewModel); + private static readonly Settings Settings = Ioc.Default.GetRequiredService(); + private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin"); + private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin"); + private readonly PluginPair _pluginPair; public PluginPair PluginPair { @@ -135,11 +141,30 @@ namespace Flow.Launcher.ViewModel => IsExpanded ? _settingControl ??= HasSettingControl - ? ((ISettingProvider)PluginPair.Plugin).CreateSettingPanel() + ? TryCreateSettingPanel(PluginPair) : null : null; private ImageSource _image = ImageLoader.MissingImage; + private static Control TryCreateSettingPanel(PluginPair pair) + { + try + { + // We can safely cast here as we already check this in HasSettingControl + return ((ISettingProvider)pair.Plugin).CreateSettingPanel(); + } + catch (Exception e) + { + // Log exception + App.API.LogException(ClassName, $"Failed to create setting panel for {pair.Metadata.Name}", e); + + // Show error message in UI + var errorMsg = string.Format(App.API.GetTranslation("errorCreatingSettingPanel"), + pair.Metadata.Name, Environment.NewLine, e.Message); + return CreateErrorSettingPanel(errorMsg); + } + } + public Visibility ActionKeywordsVisibility => PluginPair.Metadata.HideActionKeywordPanel ? Visibility.Collapsed : Visibility.Visible; public string InitializeTime => PluginPair.Metadata.InitTime + "ms"; @@ -190,5 +215,28 @@ namespace Flow.Launcher.ViewModel var changeKeywordsWindow = new ActionKeywords(this); changeKeywordsWindow.ShowDialog(); } + + private static UserControl CreateErrorSettingPanel(string text) + { + var grid = new Grid() + { + Margin = SettingPanelMargin + }; + var textBox = new TextBox + { + Text = text, + IsReadOnly = true, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Top, + TextWrapping = TextWrapping.Wrap, + Margin = SettingPanelItemTopBottomMargin + }; + textBox.SetResourceReference(TextBox.ForegroundProperty, "Color04B"); + grid.Children.Add(textBox); + return new UserControl + { + Content = grid + }; + } } } diff --git a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs index 67bbbd930..e3a0e4e44 100644 --- a/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectBrowserViewModel.cs @@ -17,8 +17,13 @@ public partial class SelectBrowserViewModel : BaseModel get => selectedCustomBrowserIndex; set { - selectedCustomBrowserIndex = value; - OnPropertyChanged(nameof(CustomBrowser)); + // When one custom browser is selected and removed, the index will become -1, so we need to ignore this change + if (value < 0) return; + if (selectedCustomBrowserIndex != value) + { + selectedCustomBrowserIndex = value; + OnPropertyChanged(nameof(CustomBrowser)); + } } } @@ -40,22 +45,12 @@ public partial class SelectBrowserViewModel : BaseModel return true; } - internal string SelectFile() - { - var dlg = new Microsoft.Win32.OpenFileDialog(); - var result = dlg.ShowDialog(); - if (result == true) - return dlg.FileName; - - return string.Empty; - } - [RelayCommand] private void Add() { CustomBrowsers.Add(new() { - Name = "New Profile" + Name = App.API.GetTranslation("defaultBrowser_new_profile") }); SelectedCustomBrowserIndex = CustomBrowsers.Count - 1; } diff --git a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs index 77f004980..f6a32e3fe 100644 --- a/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs +++ b/Flow.Launcher/ViewModel/SelectFileManagerViewModel.cs @@ -21,6 +21,8 @@ public partial class SelectFileManagerViewModel : BaseModel get => selectedCustomExplorerIndex; set { + // When one custom file manager is selected and removed, the index will become -1, so we need to ignore this change + if (value < 0) return; if (selectedCustomExplorerIndex != value) { selectedCustomExplorerIndex = value; @@ -98,27 +100,12 @@ public partial class SelectFileManagerViewModel : BaseModel } } - internal void OpenUrl(string absoluteUri) - { - App.API.OpenUrl(absoluteUri); - } - - internal string SelectFile() - { - var dlg = new Microsoft.Win32.OpenFileDialog(); - var result = dlg.ShowDialog(); - if (result == true) - return dlg.FileName; - - return string.Empty; - } - [RelayCommand] private void Add() { CustomExplorers.Add(new() { - Name = "New Profile" + Name = App.API.GetTranslation("defaultBrowser_new_profile") }); SelectedCustomExplorerIndex = CustomExplorers.Count - 1; } diff --git a/Flow.Launcher/packages.lock.json b/Flow.Launcher/packages.lock.json index 32b78c334..c90db6b0c 100644 --- a/Flow.Launcher/packages.lock.json +++ b/Flow.Launcher/packages.lock.json @@ -16,9 +16,9 @@ }, "Fody": { "type": "Direct", - "requested": "[6.9.2, )", - "resolved": "6.9.2", - "contentHash": "YBHobPGogb0vYhGYIxn/ndWqTjNWZveDi5jdjrcshL2vjwU3gQGyDeI7vGgye+2rAM5fGRvlLgNWLW3DpviS/w==" + "requested": "[6.9.3, )", + "resolved": "6.9.3", + "contentHash": "1CUGgFdyECDKgi5HaUBhdv6k+VG9Iy4OCforGfHyar3xQXAJypZkzymgKtWj/4SPd6nSG0Qi7NH71qHrDSZLaA==" }, "MdXaml": { "type": "Direct", @@ -71,41 +71,41 @@ }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct", - "requested": "[9.0.7, )", - "resolved": "9.0.7", - "contentHash": "i05AYA91vgq0as84ROVCyltD2gnxaba/f1Qw2rG7mUsS0gv8cPTr1Gm7jPQHq7JTr4MJoQUcanLVs16tIOUJaQ==", + "requested": "[9.0.9, )", + "resolved": "9.0.9", + "contentHash": "zQV2WOSP+3z1EuK91ULxfGgo2Y75bTRnmJHp08+w/YXAyekZutX/qCd88/HOMNh35MDW9mJJJxPpMPS+1Rww8A==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Hosting": { "type": "Direct", - "requested": "[9.0.7, )", - "resolved": "9.0.7", - "contentHash": "Dkv55VfitwJjPUk9mFHxT9MJAd8su7eJNaCHhBU/Y9xFqw3ZNHwrpeptXeaXiaPtfQq+alMmawIz1Impk5pHkQ==", + "requested": "[9.0.9, )", + "resolved": "9.0.9", + "contentHash": "DmRsWH3g8yZGho/pLQ79hxhM2ctE1eDTZ/HbAnrD/uw8m+P2pRRJOoBVxlrhbhMP3/y3oAJoy0yITasfmilbTg==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Configuration.Binder": "9.0.7", - "Microsoft.Extensions.Configuration.CommandLine": "9.0.7", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.7", - "Microsoft.Extensions.Configuration.FileExtensions": "9.0.7", - "Microsoft.Extensions.Configuration.Json": "9.0.7", - "Microsoft.Extensions.Configuration.UserSecrets": "9.0.7", - "Microsoft.Extensions.DependencyInjection": "9.0.7", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Diagnostics": "9.0.7", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7", - "Microsoft.Extensions.FileProviders.Physical": "9.0.7", - "Microsoft.Extensions.Hosting.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging.Configuration": "9.0.7", - "Microsoft.Extensions.Logging.Console": "9.0.7", - "Microsoft.Extensions.Logging.Debug": "9.0.7", - "Microsoft.Extensions.Logging.EventLog": "9.0.7", - "Microsoft.Extensions.Logging.EventSource": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Configuration.Binder": "9.0.9", + "Microsoft.Extensions.Configuration.CommandLine": "9.0.9", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "9.0.9", + "Microsoft.Extensions.Configuration.FileExtensions": "9.0.9", + "Microsoft.Extensions.Configuration.Json": "9.0.9", + "Microsoft.Extensions.Configuration.UserSecrets": "9.0.9", + "Microsoft.Extensions.DependencyInjection": "9.0.9", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Diagnostics": "9.0.9", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9", + "Microsoft.Extensions.FileProviders.Physical": "9.0.9", + "Microsoft.Extensions.Hosting.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging.Configuration": "9.0.9", + "Microsoft.Extensions.Logging.Console": "9.0.9", + "Microsoft.Extensions.Logging.Debug": "9.0.9", + "Microsoft.Extensions.Logging.EventLog": "9.0.9", + "Microsoft.Extensions.Logging.EventSource": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9" } }, "Microsoft.Toolkit.Uwp.Notifications": { @@ -154,9 +154,9 @@ }, "VirtualizingWrapPanel": { "type": "Direct", - "requested": "[2.3.0, )", - "resolved": "2.3.0", - "contentHash": "Dpmtcpn2HqAWZR0NkN7Qd4YCjf+sdQcemIMKm2suZVbOIB9NsmKZnYaQDIpXWTh87a9+nArVto6Od1cM2ohzCQ==" + "requested": "[2.3.1, )", + "resolved": "2.3.1", + "contentHash": "imph3SJqFFgX8vc7XRBcftfgzIL7Q+uE0Tvk7dbY0KY0tcqUCs0ZmKV3Gt9QX2745v6bSw6ns8UHpXtiptHqdA==" }, "AvalonEdit": { "type": "Transitive", @@ -196,8 +196,8 @@ }, "FSharp.Core": { "type": "Transitive", - "resolved": "9.0.300", - "contentHash": "TVt2J7RCE1KCS2IaONF+p8/KIZ1eHNbW+7qmKF6hGoD4tXl+o07ja1mPtFjMqRa5uHMFaTrGTPn/m945WnDLiQ==" + "resolved": "9.0.303", + "contentHash": "6JlV8aD8qQvcmfoe/PMOxCHXc0uX4lR23u0fAyQtnVQxYULLoTZgwgZHSnRcuUHOvS3wULFWcwdnP1iwslH60g==" }, "HtmlAgilityPack": { "type": "Transitive", @@ -211,8 +211,8 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2024.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "resolved": "2025.2.2", + "contentHash": "0X56ZRizuHdrnPpgXjWV7f2tQO1FlQg5O1967OGKnI/4ZRNOK642J8L7brM1nYvrxTTU5TP1yRyXLRLaXLPQ8A==" }, "MemoryPack": { "type": "Transitive", @@ -249,249 +249,249 @@ }, "Meziantou.Framework.Win32.Jobs": { "type": "Transitive", - "resolved": "3.4.3", - "contentHash": "REjInKnQ0OrhjjtSMPQtLtdURctCroB4L8Sd2gjTOYDysklvsdnrStx1tHS7uLv+fSyFF3aazZmo5Ka0v1oz/w==" + "resolved": "3.4.4", + "contentHash": "AivBzH5wM1NHBLehclim+o37SmireP7JxCRUoTilsc/h7LH9+YCPjb6Ig6y0khnQhFcO1P8RHYw4oiR15TGHUg==" }, "Microsoft.Extensions.Configuration": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "oxGR51+w5cXm5B9gU6XwpAB2sTiyPSmZm7hjvv0rzRnmL5o/KZzE103AuQj7sK26OBupjVzU/bZxDWvvU4nhEg==", + "resolved": "9.0.9", + "contentHash": "w87wF/90/VI0ZQBhf4rbMEeyEy0vi2WKjFmACsNAKNaorY+ZlVz7ddyXkbADvaWouMKffNmR0yQOGcrvSSvKGg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "lut/kiVvNsQ120VERMUYSFhpXPpKjjql+giy03LesASPBBcC0o6+aoFdzJH9GaYpFTQ3fGVhVjKjvJDoAW5/IQ==", + "resolved": "9.0.9", + "contentHash": "p5RKAY9POvs3axwA/AQRuJeM8AHuE8h4qbP1NxQeGm0ep46aXz1oCLAp/oOYxX1GsjStgdhHrN3XXLLXr0+b3w==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Configuration.Binder": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "ExY+zXHhU4o9KC2alp3ZdLWyVWVRSn5INqax5ABk+HEOHlAHzomhJ7ek9HHliyOMiVGoYWYaMFOGr9q59mSAGA==", + "resolved": "9.0.9", + "contentHash": "6SIp/6Bngk4jm2W36JekZbiIbFPdE/eMUtrJEqIqHGpd1zar3jvgnwxnpWQfzUiGrkyY8q8s6V82zkkEZozghA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Configuration.CommandLine": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "LqwdkMNFeRyuqExewBSaWj8roEgZH8JQ9zEAmHl5ZFcnhCvjAdHICdYVRIiSEq9RWGB731LL8kZJM8tdTKEscA==", + "resolved": "9.0.9", + "contentHash": "9bzGOcHoTi8ijrj0MHh5qUY6n9CuittZUqEOj5iE0ZJoSCfG0BI9nhcpd8MC9bOOgjZW5OeizKO8rgta9lSVyA==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Configuration.EnvironmentVariables": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "R8kgazVpDr4k1K7MeWPLAwsi5VpwrhE3ubXK38D9gpHEvf9XhZhJ8kWHKK00LDg5hJ7pMQLggdZ7XFdQ5182Ug==", + "resolved": "9.0.9", + "contentHash": "AB8suTh4STAMGDkPer5vL0YNp09eplvbkIbOfFJ1z8D1zOiFF8Hipk9FhCLU4Ea6TosWmGrK30ZIUO9KvAeFcg==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Configuration.FileExtensions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "3LVg32iMfR9ENeegXAo73L+877iOcQauLJsXlKZNVSsLA/HbPgClZdeMGdjLSkaidYw3l02XbXTlOdGYNgu91Q==", + "resolved": "9.0.9", + "contentHash": "fvgubCs++wTowHWuQ5TAyZV0S6ldA59U+tBVqFr4/WLd0oEf6ESbdBN2CFaVdn4sZqnarqMnl2O3++RG/Jrf/w==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7", - "Microsoft.Extensions.FileProviders.Physical": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9", + "Microsoft.Extensions.FileProviders.Physical": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Configuration.Json": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "3HQV326liEInT9UKEc+k73f1ECwNhvDS/DJAe5WvtMKDJTJqTH2ujrUC2ZlK/j6pXyPbV9f0Ku8JB20JveGImg==", + "resolved": "9.0.9", + "contentHash": "PiPYo1GTinR2ECM80zYdZUIFmde6jj5DryXUcOJg3yIjh+KQMQr42e+COD03QUsUiqNkJk511wVTnVpTm2AVZA==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Configuration.FileExtensions": "9.0.7", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Configuration.FileExtensions": "9.0.9", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Configuration.UserSecrets": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "ouDuPgRdeF4TJXKUh+lbm6QwyWwnCy+ijiqfFM2cI5NmW83MwKg1WNp2nCdMVcwQW8wJXteF/L9lA6ZPS3bCIQ==", + "resolved": "9.0.9", + "contentHash": "bFaNxfU8gQJX3K/Dd6XT0YIJ5ZVihdAY6Z02p2nVTUHjUsaWflLIucZOgB/ecSNnN3zbbBEf1oFC7q5NHTZIHw==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Configuration.Json": "9.0.7", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7", - "Microsoft.Extensions.FileProviders.Physical": "9.0.7" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Configuration.Json": "9.0.9", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9", + "Microsoft.Extensions.FileProviders.Physical": "9.0.9" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "iPK1FxbGFr2Xb+4Y+dTYI8Gupu9pOi8I3JPuPsrogUmEhe2hzZ9LpCmolMEBhVDo2ikcSr7G5zYiwaapHSQTew==" + "resolved": "9.0.9", + "contentHash": "/hymojfWbE9AlDOa0mczR44m00Jj+T3+HZO0ZnVTI032fVycI0ZbNOVFP6kqZMcXiLSYXzR2ilcwaRi6dzeGyA==" }, "Microsoft.Extensions.Diagnostics": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "6ykfInm6yw7pPHJACgnrPUXxUWVslFnzad44K/siXk6Ovan6fNMnXxI5X9vphHJuZ4JbMOdPIgsfTmLD+Dyxug==", + "resolved": "9.0.9", + "contentHash": "gtzl9SD6CvFYOb92qEF41Z9rICzYniM342TWbbJwN3eLS6a5fCLFvO1pQGtpMSnP3h1zHXupMEeKSA9musWYCQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.7", - "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.9", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.9" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "d39Ov1JpeWCGLCOTinlaDkujhrSAQ0HFxb7Su1BjhCKBfmDcQ6Ia1i3JI6kd3NFgwi1dexTunu82daDNwt7E6w==", + "resolved": "9.0.9", + "contentHash": "YHGmxccrVZ2Ar3eI+/NdbOHkd1/HzrHvmQ5yBsp0Gl7jTyBe6qcXNYjUt9v9JIO+Z14la44+YYEe63JSqs1fYg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "y9djCca1cz/oz/J8jTxtoecNiNvaiGBJeWd7XOPxonH+FnfHqcfslJMcSr5JMinmWFyS7eh3C9L6m6oURZ5lSA==", + "resolved": "9.0.9", + "contentHash": "M1ZhL9QkBQ/k6l/Wjgcli5zrV86HzytQ+gQiNtk9vs9Ge1fb17KKZil9T6jd15p2x/BGfXpup7Hg55CC0kkfig==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.FileProviders.Physical": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "JYEPYrb+YBpFTCdmSBrk8cg3wAi1V4so7ccq04qbhg3FQHQqgJk28L3heEOKMXcZobOBUjTnGCFJD49Ez9kG5w==", + "resolved": "9.0.9", + "contentHash": "sRrPtEwbK23OCFOQ36Xn6ofiB0/nl54/BOdR7lJ/Vwg3XlyvUdmyXvFUS1EU5ltn+sQtbcPuy1l0hsysO8++SQ==", "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7", - "Microsoft.Extensions.FileSystemGlobbing": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9", + "Microsoft.Extensions.FileSystemGlobbing": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.FileSystemGlobbing": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "5VKpTH2ME0SSs0lrtkpKgjCeHzXR5ka/H+qThPwuWi78wHubApZ/atD7w69FDt0OOM7UMV6LIbkqEQgoby4IXA==" + "resolved": "9.0.9", + "contentHash": "iQAgORaVIlkhcpxFnVEfjqNWfQCwBEEH7x2IanTwGafA6Tb4xiBoDWySTxUo3MV2NUV/PmwS/8OhT/elPnJCnw==" }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "yG2JCXAR+VqI1mKqynLPNJlNlrUJeEISEpX4UznOp2uM4IEFz3pDDauzyMvTjICutEJtOigJ1yWBvxbaIlibBw==", + "resolved": "9.0.9", + "contentHash": "ORA4dICNz7cuwupPkjXpSuoiK6GMg0aygInBIQCCFEimwoHntRKdJqB59faxq2HHJuTPW3NsZm5EjN5P5Zh6nQ==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.7", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.9", + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "fdIeQpXYV8yxSWG03cCbU2Otdrq4NWuhnQLXokWLv3L9YcK055E7u8WFJvP+uuP4CFeCEoqZQL4yPcjuXhCZrg==", + "resolved": "9.0.9", + "contentHash": "MaCB0Y9hNDs4YLu3HCJbo199WnJT8xSgajG1JYGANz9FkseQ5f3v/llu3HxLI6mjDlu7pa7ps9BLPWjKzsAAzQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7" + "Microsoft.Extensions.DependencyInjection": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "sMM6NEAdUTE/elJ2wqjOi0iBWqZmSyaTByLF9e8XHv6DRJFFnOe0N+s8Uc6C91E4SboQCfLswaBIZ+9ZXA98AA==", + "resolved": "9.0.9", + "contentHash": "FEgpSF+Z9StMvrsSViaybOBwR0f0ZZxDm8xV5cSOFiXN/t+ys+rwAlTd/6yG7Ld1gfppgvLcMasZry3GsI9lGA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Logging.Configuration": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "AEBty9rvFGvdFRqgIDEhQmiCnIfQWyzVoOZrO244cfu+n9M+wI1QLDpuROVILlplIBtLVmOezAF7d1H3Qog6Xw==", + "resolved": "9.0.9", + "contentHash": "Abuo+S0Sg+Ke6vzSh5Ell+lwJJM+CEIqg1ImtWnnqF6a/ibJkQnmFJi4/ekEw/0uAcdFKJXtGV7w6cFN0nyXeg==", "dependencies": { - "Microsoft.Extensions.Configuration": "9.0.7", - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Configuration.Binder": "9.0.7", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7", - "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.7" + "Microsoft.Extensions.Configuration": "9.0.9", + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Configuration.Binder": "9.0.9", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9", + "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.9" } }, "Microsoft.Extensions.Logging.Console": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "pEHlNa8iCfKsBFA3YVDn/8EicjSU/m8uDfyoR0i4svONDss4Yu9Kznw53E/TyI+TveTo7CwRid4kfd4pLYXBig==", + "resolved": "9.0.9", + "contentHash": "x3+W7IfW9Tg3sV+sU9N1039M4CqklaAecwhz9qNtjOCBdmg7h96JaL+NAvhYgZgweVJTJaxAvuO8I+ZZehE7Pg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging.Configuration": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging.Configuration": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9" } }, "Microsoft.Extensions.Logging.Debug": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "MxzZj7XbsYJwfjclVTjJym2/nVIkksu7l7tC/4HYy+YRdDmpE4B+hTzCXu3BNfLNhdLPZsWpyXuYe6UGgWDm3g==", + "resolved": "9.0.9", + "contentHash": "q8IbjIzTjfaGfuf9LAuG3X9BytAWj2hWhLU61rEkit847oaSSbcdx/yybY3yL9RgVG1u9ctk7kbCv18M+7Fi6Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9" } }, "Microsoft.Extensions.Logging.EventLog": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "usrMVsY7c8M8fESt34Y3eEIQIlRlKXfPDlI+vYEb6xT7SUjhua2ey3NpHgQktiTgz8Uo5RiWqGD8ieiyo2WaDA==", + "resolved": "9.0.9", + "contentHash": "1SX5+mv16SBb5NrtLNxIvUt8PHbdvDloZazQdxz1CNM39jG7yeF6olH3sceQ4ONF0oVD5mVUsTag0iVX4xgyog==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7", - "System.Diagnostics.EventLog": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9", + "System.Diagnostics.EventLog": "9.0.9" } }, "Microsoft.Extensions.Logging.EventSource": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "/wwi6ckTEegCExFV6gVToCO7CvysZnmE50fpdkYUsSMh0ue9vRkQ7uOqkHyHol93ASYTEahrp+guMtS/+fZKaA==", + "resolved": "9.0.9", + "contentHash": "rGQi5mImot7tTFxj1tQWknWjOBHX1+gsX1WLmQNl5WHr4Sx1kXUBGDuRUjfx4c8pe/hcYHdalAmgk7RdusW6Jw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Logging": "9.0.7", - "Microsoft.Extensions.Logging.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Logging": "9.0.9", + "Microsoft.Extensions.Logging.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "trJnF6cRWgR5uMmHpGoHmM1wOVFdIYlELlkO9zX+RfieK0321Y55zrcs4AaEymKup7dxgEN/uJU25CAcMNQRXw==", + "resolved": "9.0.9", + "contentHash": "loxGGHE1FC2AefwPHzrjPq7X92LQm64qnU/whKfo6oWaceewPUVYQJBJs3S3E2qlWwnCpeZ+dGCPTX+5dgVAuQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Options.ConfigurationExtensions": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "pE/jeAWHEIy/8HsqYA+I1+toTsdvsv+WywAcRoNSvPoFwjOREa8Fqn7D0/i0PbiXsDLFupltTTctliePx8ib4w==", + "resolved": "9.0.9", + "contentHash": "n4DCdnn2qs6V5U06Sx62FySEAZsJiJJgOzrPHDh9hPK7c2W8hEabC76F3Re3tGPjpiKa02RvB6FxZyxo8iICzg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.7", - "Microsoft.Extensions.Configuration.Binder": "9.0.7", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.7", - "Microsoft.Extensions.Options": "9.0.7", - "Microsoft.Extensions.Primitives": "9.0.7" + "Microsoft.Extensions.Configuration.Abstractions": "9.0.9", + "Microsoft.Extensions.Configuration.Binder": "9.0.9", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.9", + "Microsoft.Extensions.Options": "9.0.9", + "Microsoft.Extensions.Primitives": "9.0.9" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "ti/zD9BuuO50IqlvhWQs9GHxkCmoph5BHjGiWKdg2t6Or8XoyAfRJiKag+uvd/fpASnNklfsB01WpZ4fhAe0VQ==" + "resolved": "9.0.9", + "contentHash": "z4pyMePOrl733ltTowbN565PxBw1oAr8IHmIXNDiDqd22nFpYltX9KhrNC/qBWAG1/Zx5MHX+cOYhWJQYCO/iw==" }, "Microsoft.IO.RecyclableMemoryStream": { "type": "Transitive", @@ -590,15 +590,15 @@ }, "NLog": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "qDWiqy8/xdpZKtHna/645KbalwP86N2NFJEzfqhcv+Si4V2iNaEfR/dCneuF/4+Dcwl3f7jHMXj3ndWYftV3Ug==" + "resolved": "6.0.4", + "contentHash": "Xr+lIk1ZlTTFXEqnxQVLxrDqZlt2tm5X+/AhJbaY2emb/dVtGDiU5QuEtj3gHtwV/SWlP/rJ922I/BPuOJXlRw==" }, "NLog.OutputDebugString": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "wwJCQLaHVzuRf8TsXB+EEdrzVvE3dnzCSMQMDgwkw3AXp8VSp3JSVF/Q/H0oEqggKgKhPs13hh3a7svyQr4s3A==", + "resolved": "6.0.4", + "contentHash": "TOP2Ap9BbE98B/l/TglnguowOD0rXo8B/20xAgvj9shO/kf6IJ5M4QMhVxq72mrneJ/ANhHY7Jcd+xJbzuI5PA==", "dependencies": { - "NLog": "6.0.1" + "NLog": "6.0.4" } }, "runtime.osx.10.10-x64.CoreCompat.System.Drawing": { @@ -608,8 +608,8 @@ }, "SharpVectors.Wpf": { "type": "Transitive", - "resolved": "1.8.4.2", - "contentHash": "PNxLkMBJnV8A+6yH9OqOlhLJegvWP/dvh0rAJp2l0kcrR+rB4R2tQ9vhUqka+UilH4atN8T6zvjDOizVyfz2Ng==" + "resolved": "1.8.5", + "contentHash": "WURdBDq5AE8RjKV9pFS7lNkJe81gxja9SaMGE4URq9GJUZ6M+5DGUL0Lm3B0iYW2/Meyowaz4ffGsyW+RBSTtg==" }, "Splat": { "type": "Transitive", @@ -672,8 +672,8 @@ }, "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "9.0.7", - "contentHash": "AJ+9fyCtQUImntxAJ9l4PZiCd4iepuk4pm7Qcno7PBIWQnfXlvwKuFsGk2H+QyY69GUVzDP2heELW6ho5BCXUg==" + "resolved": "9.0.9", + "contentHash": "wpsUfnyv8E5K4WQaok6weewvAbQhcLwXFcHBm5U0gdEaBs85N//ssuYvRPFWwz2rO/9/DFP3A1sGMzUFBj8y3w==" }, "System.Drawing.Common": { "type": "Transitive", @@ -838,10 +838,10 @@ "type": "Project", "dependencies": { "Droplex": "[1.7.0, )", - "FSharp.Core": "[9.0.300, )", + "FSharp.Core": "[9.0.303, )", "Flow.Launcher.Infrastructure": "[1.0.0, )", - "Flow.Launcher.Plugin": "[4.7.0, )", - "Meziantou.Framework.Win32.Jobs": "[3.4.3, )", + "Flow.Launcher.Plugin": "[5.0.0, )", + "Meziantou.Framework.Win32.Jobs": "[3.4.4, )", "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "SemanticVersioning": "[3.0.0, )", "StreamJsonRpc": "[2.22.11, )", @@ -854,14 +854,14 @@ "Ben.Demystifier": "[0.4.1, )", "BitFaster.Caching": "[2.5.4, )", "CommunityToolkit.Mvvm": "[8.4.0, )", - "Flow.Launcher.Plugin": "[4.7.0, )", + "Flow.Launcher.Plugin": "[5.0.0, )", "InputSimulator": "[1.0.4, )", "MemoryPack": "[1.21.4, )", "Microsoft.VisualStudio.Threading": "[17.14.15, )", "NHotkey.Wpf": "[3.0.0, )", - "NLog": "[6.0.1, )", - "NLog.OutputDebugString": "[6.0.1, )", - "SharpVectors.Wpf": "[1.8.4.2, )", + "NLog": "[6.0.4, )", + "NLog.OutputDebugString": "[6.0.4, )", + "SharpVectors.Wpf": "[1.8.5, )", "System.Drawing.Common": "[7.0.0, )", "ToolGood.Words.Pinyin": "[3.1.0.3, )" } @@ -869,7 +869,7 @@ "flow.launcher.plugin": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2024.3.0, )" + "JetBrains.Annotations": "[2025.2.2, )" } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 901dc2a37..e3233f73d 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -34,6 +34,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -103,9 +104,9 @@ - - - + + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs index 1820a7836..82b089033 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs @@ -106,12 +106,13 @@ public static class FaviconHelper { try { - using (var image = SKImage.FromBitmap(bitmap)) - using (var webp = image.Encode(SKEncodedImageFormat.Webp, 65)) - { - if (webp != null) - return webp.ToArray(); - } + using var image = SKImage.FromBitmap(bitmap); + if (image is null) + return null; + + using var webp = image.Encode(SKEncodedImageFormat.Webp, 65); + if (webp != null) + return webp.ToArray(); } finally { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 43a2c2f3c..20a0ec4f0 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -33,6 +33,7 @@ prompt 4 false + $(NoWarn);FLSG0007 @@ -62,7 +63,7 @@ - + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml index b71e5d8a0..b12972b1b 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/en.xaml @@ -4,7 +4,7 @@ xmlns:system="clr-namespace:System;assembly=mscorlib"> Calculator - Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place. + Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'. Not a number (NaN) Expression wrong or incomplete (Did you forget some parentheses?) Copy this number to the clipboard @@ -15,4 +15,5 @@ Dot (.) Max. decimal places Copy failed, please try later + Show error message when calculation fails \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 6878c54b4..9d5e4700f 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -13,30 +13,24 @@ namespace Flow.Launcher.Plugin.Calculator { public class Main : IPlugin, IPluginI18n, ISettingProvider { - private static readonly Regex RegValidExpressChar = MainRegexHelper.GetRegValidExpressChar(); - private static readonly Regex RegBrackets = MainRegexHelper.GetRegBrackets(); private static readonly Regex ThousandGroupRegex = MainRegexHelper.GetThousandGroupRegex(); private static readonly Regex NumberRegex = MainRegexHelper.GetNumberRegex(); + private static readonly Regex PowRegex = MainRegexHelper.GetPowRegex(); + private static readonly Regex LogRegex = MainRegexHelper.GetLogRegex(); + private static readonly Regex LnRegex = MainRegexHelper.GetLnRegex(); + private static readonly Regex FunctionRegex = MainRegexHelper.GetFunctionRegex(); private static Engine MagesEngine; private const string Comma = ","; private const string Dot = "."; + private const string IcoPath = "Images/calculator.png"; + private static readonly List EmptyResults = []; internal static PluginInitContext Context { get; set; } = null!; private Settings _settings; private SettingsViewModel _viewModel; - /// - /// Holds the formatting information for a single query. - /// This is used to ensure thread safety by keeping query state local. - /// - private class ParsingContext - { - public string InputDecimalSeparator { get; set; } - public bool InputUsesGroupSeparators { get; set; } - } - public void Init(PluginInitContext context) { Context = context; @@ -54,38 +48,98 @@ namespace Flow.Launcher.Plugin.Calculator public List Query(Query query) { - if (!CanCalculate(query)) + if (string.IsNullOrWhiteSpace(query.Search)) { - return new List(); + return EmptyResults; } - var context = new ParsingContext(); - try { - var expression = NumberRegex.Replace(query.Search, m => NormalizeNumber(m.Value, context)); + var search = query.Search; + bool isFunctionPresent = FunctionRegex.IsMatch(search); + + // Mages is case sensitive, so we need to convert all function names to lower case. + search = FunctionRegex.Replace(search, m => m.Value.ToLowerInvariant()); + + var decimalSep = GetDecimalSeparator(); + var groupSep = GetGroupSeparator(decimalSep); + var expression = NumberRegex.Replace(search, m => NormalizeNumber(m.Value, isFunctionPresent, decimalSep, groupSep)); + + // WORKAROUND START: The 'pow' function in Mages v3.0.0 is broken. + // https://github.com/FlorianRappl/Mages/issues/132 + // We bypass it by rewriting any pow(x,y) expression to the equivalent (x^y) expression + // before the engine sees it. This loop handles nested calls. + { + string previous; + do + { + previous = expression; + expression = PowRegex.Replace(previous, PowMatchEvaluator); + } while (previous != expression); + } + // WORKAROUND END + + // WORKAROUND START: The 'log' & 'ln' function in Mages v3.0.0 are broken. + // https://github.com/FlorianRappl/Mages/issues/137 + // We bypass it by rewriting any log & ln expression to the equivalent (log10 & log) expression + // before the engine sees it. This loop handles nested calls. + { + string previous; + do + { + previous = expression; + expression = LogRegex.Replace(previous, LogMatchEvaluator); + } while (previous != expression); + } + { + string previous; + do + { + previous = expression; + expression = LnRegex.Replace(previous, LnMatchEvaluator); + } while (previous != expression); + } + // WORKAROUND END var result = MagesEngine.Interpret(expression); - if (result?.ToString() == "NaN") + if (result == null || string.IsNullOrEmpty(result.ToString())) + { + if (!_settings.ShowErrorMessage) return EmptyResults; + return + [ + new Result + { + Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(), + IcoPath = IcoPath + } + ]; + } + + if (result.ToString() == "NaN") + { result = Localize.flowlauncher_plugin_calculator_not_a_number(); + } if (result is Function) + { result = Localize.flowlauncher_plugin_calculator_expression_not_complete(); + } - if (!string.IsNullOrEmpty(result?.ToString())) + if (!string.IsNullOrEmpty(result.ToString())) { decimal roundedResult = Math.Round(Convert.ToDecimal(result), _settings.MaxDecimalPlaces, MidpointRounding.AwayFromZero); - string newResult = FormatResult(roundedResult, context); + string newResult = FormatResult(roundedResult); - return new List - { + return + [ new Result { Title = newResult, - IcoPath = "Images/calculator.png", + IcoPath = IcoPath, Score = 300, - SubTitle = Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(), + // Check context nullability for unit testing + SubTitle = Context == null ? string.Empty : Localize.flowlauncher_plugin_calculator_copy_number_to_clipboard(), CopyText = newResult, Action = c => { @@ -101,118 +155,206 @@ namespace Flow.Launcher.Plugin.Calculator } } } - }; + ]; } } catch (Exception) { - // ignored + // Mages engine can throw various exceptions, for simplicity we catch them all and show a generic message. + if (!_settings.ShowErrorMessage) return EmptyResults; + return + [ + new Result + { + Title = Localize.flowlauncher_plugin_calculator_expression_not_complete(), + IcoPath = IcoPath + } + ]; } - return new List(); + return EmptyResults; } - /// - /// Parses a string representation of a number, detecting its format. It uses structural analysis - /// and falls back to system culture for truly ambiguous cases (e.g., "1,234"). - /// It populates the provided ParsingContext with the detected format for later use. - /// - /// A normalized number string with '.' as the decimal separator for the Mages engine. - private string NormalizeNumber(string numberStr, ParsingContext context) + private static string PowMatchEvaluator(Match m) { - var systemGroupSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator; - int dotCount = numberStr.Count(f => f == '.'); - int commaCount = numberStr.Count(f => f == ','); + // m.Groups[1].Value will be `(...)` with parens + var contentWithParen = m.Groups[1].Value; + // remove outer parens. `(min(2,3), 4)` becomes `min(2,3), 4` + var argsContent = contentWithParen[1..^1]; - // Case 1: Unambiguous mixed separators (e.g., "1.234,56") - if (dotCount > 0 && commaCount > 0) + var bracketCount = 0; + var splitIndex = -1; + + // Find the top-level comma that separates the two arguments of pow. + for (var i = 0; i < argsContent.Length; i++) { - context.InputUsesGroupSeparators = true; - if (numberStr.LastIndexOf('.') > numberStr.LastIndexOf(',')) + switch (argsContent[i]) { - context.InputDecimalSeparator = Dot; - return numberStr.Replace(Comma, string.Empty); + case '(': + case '[': + bracketCount++; + break; + case ')': + case ']': + bracketCount--; + break; + case ',' when bracketCount == 0: + splitIndex = i; + break; + } + + if (splitIndex != -1) + break; + } + + if (splitIndex == -1) + { + // This indicates malformed arguments for pow, e.g., pow(5) or pow(). + // Return original string to let Mages handle the error. + return m.Value; + } + + var arg1 = argsContent[..splitIndex].Trim(); + var arg2 = argsContent[(splitIndex + 1)..].Trim(); + + // Check for empty arguments which can happen with stray commas, e.g., pow(,5) + if (string.IsNullOrEmpty(arg1) || string.IsNullOrEmpty(arg2)) + { + return m.Value; + } + + return $"({arg1}^{arg2})"; + } + + private static string LogMatchEvaluator(Match m) + { + // m.Groups[1].Value will be `(...)` with parens + var contentWithParen = m.Groups[1].Value; + var argsContent = contentWithParen[1..^1]; + + // log is unary — if malformed, return original to let Mages handle it + var arg = argsContent.Trim(); + if (string.IsNullOrEmpty(arg)) return m.Value; + + // log(x) -> log10(x) (natural log) + return $"(log10({arg}))"; + } + + private static string LnMatchEvaluator(Match m) + { + // m.Groups[1].Value will be `(...)` with parens + var contentWithParen = m.Groups[1].Value; + var argsContent = contentWithParen[1..^1]; + + // ln is unary — if malformed, return original to let Mages handle it + var arg = argsContent.Trim(); + if (string.IsNullOrEmpty(arg)) return m.Value; + + // ln(x) -> log(x) (natural log) + return $"(log({arg}))"; + } + private static string NormalizeNumber(string numberStr, bool isFunctionPresent, string decimalSep, string groupSep) + { + if (isFunctionPresent) + { + // STRICT MODE: When functions are present, ',' is ALWAYS an argument separator. + if (numberStr.Contains(',')) + { + return numberStr; + } + + string processedStr = numberStr; + + // Handle group separator, with special care for ambiguous dot. + if (!string.IsNullOrEmpty(groupSep)) + { + if (groupSep == ".") + { + var parts = processedStr.Split('.'); + if (parts.Length > 1) + { + var culture = CultureInfo.CurrentCulture; + if (IsValidGrouping(parts, culture.NumberFormat.NumberGroupSizes)) + { + processedStr = processedStr.Replace(groupSep, ""); + } + // If not grouped, it's likely a decimal number, so we don't strip dots. + } + } + else + { + processedStr = processedStr.Replace(groupSep, ""); + } + } + + // Handle decimal separator. + if (decimalSep != ".") + { + processedStr = processedStr.Replace(decimalSep, "."); + } + + return processedStr; + } + else + { + // LENIENT MODE: No functions are present, so we can be flexible. + string processedStr = numberStr; + if (!string.IsNullOrEmpty(groupSep)) + { + processedStr = processedStr.Replace(groupSep, ""); + } + if (decimalSep != ".") + { + processedStr = processedStr.Replace(decimalSep, "."); + } + return processedStr; + } + } + + private static bool IsValidGrouping(string[] parts, int[] groupSizes) + { + if (parts.Length <= 1) return true; + + if (groupSizes is null || groupSizes.Length == 0 || groupSizes[0] == 0) + return false; // has groups, but culture defines none. + + var firstPart = parts[0]; + if (firstPart.StartsWith('-')) firstPart = firstPart[1..]; + if (firstPart.Length == 0) return false; // e.g. ",123" + + if (firstPart.Length > groupSizes[0]) return false; + + var lastGroupSize = groupSizes.Last(); + var canRepeatLastGroup = lastGroupSize != 0; + + int groupIndex = 0; + for (int i = parts.Length - 1; i > 0; i--) + { + int expectedSize; + if (groupIndex < groupSizes.Length) + { + expectedSize = groupSizes[groupIndex]; + } + else if(canRepeatLastGroup) + { + expectedSize = lastGroupSize; } else { - context.InputDecimalSeparator = Comma; - return numberStr.Replace(Dot, string.Empty).Replace(Comma, Dot); + return false; } + + if (parts[i].Length != expectedSize) return false; + + groupIndex++; } - // Case 2: Only dots - if (dotCount > 0) - { - if (dotCount > 1) - { - context.InputUsesGroupSeparators = true; - return numberStr.Replace(Dot, string.Empty); - } - // A number is ambiguous if it has a single Dot in the thousands position, - // and does not start with a "0." or "." - bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf('.') == 4 - && !numberStr.StartsWith("0.") - && !numberStr.StartsWith("."); - if (isAmbiguous) - { - if (systemGroupSep == Dot) - { - context.InputUsesGroupSeparators = true; - return numberStr.Replace(Dot, string.Empty); - } - else - { - context.InputDecimalSeparator = Dot; - return numberStr; - } - } - else // Unambiguous decimal (e.g., "12.34" or "0.123" or ".123") - { - context.InputDecimalSeparator = Dot; - return numberStr; - } - } - - // Case 3: Only commas - if (commaCount > 0) - { - if (commaCount > 1) - { - context.InputUsesGroupSeparators = true; - return numberStr.Replace(Comma, string.Empty); - } - // A number is ambiguous if it has a single Comma in the thousands position, - // and does not start with a "0," or "," - bool isAmbiguous = numberStr.Length - numberStr.LastIndexOf(',') == 4 - && !numberStr.StartsWith("0,") - && !numberStr.StartsWith(","); - if (isAmbiguous) - { - if (systemGroupSep == Comma) - { - context.InputUsesGroupSeparators = true; - return numberStr.Replace(Comma, string.Empty); - } - else - { - context.InputDecimalSeparator = Comma; - return numberStr.Replace(Comma, Dot); - } - } - else // Unambiguous decimal (e.g., "12,34" or "0,123" or ",123") - { - context.InputDecimalSeparator = Comma; - return numberStr.Replace(Comma, Dot); - } - } - - // Case 4: No separators - return numberStr; + return true; } - private string FormatResult(decimal roundedResult, ParsingContext context) + private string FormatResult(decimal roundedResult) { - string decimalSeparator = context.InputDecimalSeparator ?? GetDecimalSeparator(); + string decimalSeparator = GetDecimalSeparator(); string groupSeparator = GetGroupSeparator(decimalSeparator); string resultStr = roundedResult.ToString(CultureInfo.InvariantCulture); @@ -221,7 +363,7 @@ namespace Flow.Launcher.Plugin.Calculator string integerPart = parts[0]; string fractionalPart = parts.Length > 1 ? parts[1] : string.Empty; - if (context.InputUsesGroupSeparators && integerPart.Length > 3) + if (integerPart.Length > 3) { integerPart = ThousandGroupRegex.Replace(integerPart, groupSeparator); } @@ -236,29 +378,23 @@ namespace Flow.Launcher.Plugin.Calculator private string GetGroupSeparator(string decimalSeparator) { - // This logic is now independent of the system's group separator - // to ensure consistent output for unit testing. - return decimalSeparator == Dot ? Comma : Dot; - } + var culture = CultureInfo.CurrentCulture; + var systemGroupSeparator = culture.NumberFormat.NumberGroupSeparator; - private bool CanCalculate(Query query) - { - if (query.Search.Length < 2) + if (_settings.DecimalSeparator == DecimalSeparator.UseSystemLocale) { - return false; + return systemGroupSeparator; } - if (!RegValidExpressChar.IsMatch(query.Search)) + // When a custom decimal separator is used, + // use the system's group separator unless it conflicts with the custom decimal separator. + if (decimalSeparator == systemGroupSeparator) { - return false; + // Conflict: use the opposite of the decimal separator as a fallback. + return decimalSeparator == Dot ? Comma : Dot; } - if (!IsBracketComplete(query.Search)) - { - return false; - } - - return true; + return systemGroupSeparator; } private string GetDecimalSeparator() @@ -273,25 +409,6 @@ namespace Flow.Launcher.Plugin.Calculator }; } - private static bool IsBracketComplete(string query) - { - var matchs = RegBrackets.Matches(query); - var leftBracketCount = 0; - foreach (Match match in matchs) - { - if (match.Value == "(" || match.Value == "[") - { - leftBracketCount++; - } - else - { - leftBracketCount--; - } - } - - return leftBracketCount == 0; - } - public string GetTranslatedPluginTitle() { return Localize.flowlauncher_plugin_calculator_plugin_name(); diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs index f4e2090e7..a8b582ccc 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/MainRegexHelper.cs @@ -4,16 +4,21 @@ namespace Flow.Launcher.Plugin.Calculator; internal static partial class MainRegexHelper { - - [GeneratedRegex(@"[\(\)\[\]]", RegexOptions.Compiled)] - public static partial Regex GetRegBrackets(); - - [GeneratedRegex(@"^(ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|sin|cos|tan|arcsin|arccos|arctan|eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|bin2dec|hex2dec|oct2dec|factorial|sign|isprime|isinfty|==|~=|&&|\|\||(?:\<|\>)=?|[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]])+$", RegexOptions.Compiled)] - public static partial Regex GetRegValidExpressChar(); - - [GeneratedRegex(@"[\d\.,]+", RegexOptions.Compiled)] + [GeneratedRegex(@"-?[\d\.,'\u00A0\u202F]+", RegexOptions.Compiled | RegexOptions.CultureInvariant)] public static partial Regex GetNumberRegex(); [GeneratedRegex(@"\B(?=(\d{3})+(?!\d))", RegexOptions.Compiled)] public static partial Regex GetThousandGroupRegex(); + + [GeneratedRegex(@"\bpow(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + public static partial Regex GetPowRegex(); + + [GeneratedRegex(@"\blog(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + public static partial Regex GetLogRegex(); + + [GeneratedRegex(@"\bln(\((?:[^()\[\]]|\((?)|\)(?<-Depth>)|\[(?)|\](?<-Depth>))*(?(Depth)(?!))\))", RegexOptions.Compiled | RegexOptions.RightToLeft | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + public static partial Regex GetLnRegex(); + + [GeneratedRegex(@"\b(sqrt|pow|factorial|abs|sign|ceil|floor|round|exp|log|log2|log10|min|max|lt|eq|gt|sin|cos|tan|arcsin|arccos|arctan|isnan|isint|isprime|isinfty|rand|randi|type|is|as|length|throw|catch|eval|map|clamp|lerp|regex|shuffle)\s*\(", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)] + public static partial Regex GetFunctionRegex(); } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index 8354863b8..cac0f3080 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,9 +1,11 @@  -namespace Flow.Launcher.Plugin.Calculator +namespace Flow.Launcher.Plugin.Calculator; + +public class Settings { - public class Settings - { - public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale; - public int MaxDecimalPlaces { get; set; } = 10; - } + public DecimalSeparator DecimalSeparator { get; set; } = DecimalSeparator.UseSystemLocale; + + public int MaxDecimalPlaces { get; set; } = 10; + + public bool ShowErrorMessage { get; set; } = false; } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs index 87ae72fb6..79236bdf8 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs @@ -1,31 +1,25 @@ using System.Collections.Generic; using System.Linq; -namespace Flow.Launcher.Plugin.Calculator.ViewModels +namespace Flow.Launcher.Plugin.Calculator.ViewModels; + +public class SettingsViewModel(Settings settings) : BaseModel { - public class SettingsViewModel : BaseModel + public Settings Settings { get; } = settings; + + public static IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20); + + public List AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues(); + + public DecimalSeparator SelectedDecimalSeparator { - public SettingsViewModel(Settings settings) + get => Settings.DecimalSeparator; + set { - Settings = settings; - } - - public Settings Settings { get; init; } - - public static IEnumerable MaxDecimalPlacesRange => Enumerable.Range(1, 20); - - public List AllDecimalSeparator { get; } = DecimalSeparatorLocalized.GetValues(); - - public DecimalSeparator SelectedDecimalSeparator - { - get => Settings.DecimalSeparator; - set + if (Settings.DecimalSeparator != value) { - if (Settings.DecimalSeparator != value) - { - Settings.DecimalSeparator = value; - OnPropertyChanged(); - } + Settings.DecimalSeparator = value; + OnPropertyChanged(); } } } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml index 8d240ef39..9e7549b2d 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -15,6 +15,7 @@ + @@ -58,5 +59,14 @@ ItemsSource="{Binding MaxDecimalPlacesRange}" SelectedItem="{Binding Settings.MaxDecimalPlaces}" /> + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs index 7bc307d11..9e75e7bfb 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml.cs @@ -1,22 +1,16 @@ using System.Windows.Controls; using Flow.Launcher.Plugin.Calculator.ViewModels; -namespace Flow.Launcher.Plugin.Calculator.Views -{ - /// - /// Interaction logic for CalculatorSettings.xaml - /// - public partial class CalculatorSettings : UserControl - { - private readonly SettingsViewModel _viewModel; - private readonly Settings _settings; +namespace Flow.Launcher.Plugin.Calculator.Views; - public CalculatorSettings(Settings settings) - { - _viewModel = new SettingsViewModel(settings); - _settings = _viewModel.Settings; - DataContext = _viewModel; - InitializeComponent(); - } +public partial class CalculatorSettings : UserControl +{ + private readonly SettingsViewModel _viewModel; + + public CalculatorSettings(Settings settings) + { + _viewModel = new SettingsViewModel(settings); + DataContext = _viewModel; + InitializeComponent(); } } diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json index c9435e043..93df9ec72 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json @@ -2,7 +2,7 @@ "ID": "CEA0FDFC6D3B4085823D60DC76F28855", "ActionKeyword": "*", "Name": "Calculator", - "Description": "Perform mathematical calculations (including hexadecimal values). Use ',' or '.' as thousand separator or decimal place.", + "Description": "Perform mathematical calculations, including hex values and advanced functions such as 'min(1,2,3)', 'sqrt(123)' and 'cos(123)'.", "Author": "cxfksword, dcog989", "Version": "1.0.0", "Language": "csharp", diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 3802c701b..90db87966 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -66,8 +66,8 @@ namespace Flow.Launcher.Plugin.Explorer { contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_title"), - SubTitle = Context.API.GetTranslation("plugin_explorer_add_to_quickaccess_subtitle"), + Title = Localize.plugin_explorer_add_to_quickaccess_title(), + SubTitle = Localize.plugin_explorer_add_to_quickaccess_subtitle(), Action = (context) => { Settings.QuickAccessLinks.Add(new AccessLink @@ -77,16 +77,14 @@ namespace Flow.Launcher.Plugin.Explorer Type = record.Type }); - Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"), - Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess_detail"), - Constants.ExplorerIconImageFullPath); - - + Context.API.ShowMsg(Localize.plugin_explorer_addfilefoldersuccess(), + Localize.plugin_explorer_addfilefoldersuccess_detail(), + Constants.ExplorerIconImageFullPath); return true; }, - SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"), - TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_titletooltip"), + SubTitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(), + TitleToolTip = Localize.plugin_explorer_contextmenu_titletooltip(), IcoPath = Constants.QuickAccessImagePath, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue718"), }); @@ -95,22 +93,20 @@ namespace Flow.Launcher.Plugin.Explorer { contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_title"), - SubTitle = Context.API.GetTranslation("plugin_explorer_remove_from_quickaccess_subtitle"), + Title = Localize.plugin_explorer_remove_from_quickaccess_title(), + SubTitle = Localize.plugin_explorer_remove_from_quickaccess_subtitle(), Action = (context) => { Settings.QuickAccessLinks.Remove(Settings.QuickAccessLinks.FirstOrDefault(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase))); - Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess"), - Context.API.GetTranslation("plugin_explorer_removefilefoldersuccess_detail"), - Constants.ExplorerIconImageFullPath); - - + Context.API.ShowMsg(Localize.plugin_explorer_removefilefoldersuccess(), + Localize.plugin_explorer_removefilefoldersuccess_detail(), + Constants.ExplorerIconImageFullPath); return true; }, - SubTitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"), - TitleToolTip = Context.API.GetTranslation("plugin_explorer_contextmenu_remove_titletooltip"), + SubTitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(), + TitleToolTip = Localize.plugin_explorer_contextmenu_remove_titletooltip(), IcoPath = Constants.RemoveQuickAccessImagePath, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uecc9") }); @@ -118,8 +114,8 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_copypath"), - SubTitle = Context.API.GetTranslation("plugin_explorer_copypath_subtitle"), + Title = Localize.plugin_explorer_copypath(), + SubTitle = Localize.plugin_explorer_copypath_subtitle(), Action = _ => { try @@ -130,7 +126,7 @@ namespace Flow.Launcher.Plugin.Explorer catch (Exception e) { LogException("Fail to set text in clipboard", e); - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text")); + Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text()); return false; } }, @@ -140,8 +136,8 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_copyname"), - SubTitle = Context.API.GetTranslation("plugin_explorer_copyname_subtitle"), + Title = Localize.plugin_explorer_copyname(), + SubTitle = Localize.plugin_explorer_copyname_subtitle(), Action = _ => { try @@ -152,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer catch (Exception e) { LogException("Fail to set text in clipboard", e); - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_text")); + Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_text()); return false; } }, @@ -162,8 +158,8 @@ namespace Flow.Launcher.Plugin.Explorer contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_copyfilefolder"), - SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_copyfile_subtitle") : Context.API.GetTranslation("plugin_explorer_copyfolder_subtitle"), + Title = Localize.plugin_explorer_copyfilefolder(), + SubTitle = isFile ? Localize.plugin_explorer_copyfile_subtitle(): Localize.plugin_explorer_copyfolder_subtitle(), Action = _ => { try @@ -174,28 +170,26 @@ namespace Flow.Launcher.Plugin.Explorer catch (Exception e) { LogException($"Fail to set file/folder in clipboard", e); - Context.API.ShowMsgError(Context.API.GetTranslation("plugin_explorer_fail_to_set_files")); + Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_set_files()); return false; } - }, IcoPath = icoPath, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf12b") }); - if (record.Type is ResultType.File or ResultType.Folder) contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_deletefilefolder"), - SubTitle = isFile ? Context.API.GetTranslation("plugin_explorer_deletefile_subtitle") : Context.API.GetTranslation("plugin_explorer_deletefolder_subtitle"), + Title = Localize.plugin_explorer_deletefilefolder(), + SubTitle = isFile ? Localize.plugin_explorer_deletefile_subtitle(): Localize.plugin_explorer_deletefolder_subtitle(), Action = (context) => { try { if (Context.API.ShowMsgBox( - string.Format(Context.API.GetTranslation("plugin_explorer_delete_folder_link"), record.FullPath), - Context.API.GetTranslation("plugin_explorer_deletefilefolder"), + Localize.plugin_explorer_delete_folder_link(record.FullPath), + Localize.plugin_explorer_deletefilefolder(), MessageBoxButton.OKCancel, MessageBoxImage.Warning) == MessageBoxResult.Cancel) @@ -208,15 +202,15 @@ namespace Flow.Launcher.Plugin.Explorer _ = Task.Run(() => { - Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess"), - string.Format(Context.API.GetTranslation("plugin_explorer_deletefilefoldersuccess_detail"), record.FullPath), + Context.API.ShowMsg(Localize.plugin_explorer_deletefilefoldersuccess(), + Localize.plugin_explorer_deletefilefoldersuccess_detail(record.FullPath), Constants.ExplorerIconImageFullPath); }); } catch (Exception e) { LogException($"Fail to delete {record.FullPath}", e); - Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_delete"), record.FullPath)); + Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_delete(record.FullPath)); return false; } @@ -230,7 +224,7 @@ namespace Flow.Launcher.Plugin.Explorer { contextMenus.Add(new Result() { - Title = Context.API.GetTranslation("plugin_explorer_show_contextmenu_title"), + Title = Localize.plugin_explorer_show_contextmenu_title(), IcoPath = Constants.ShowContextMenuImagePath, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue700"), Action = _ => @@ -248,8 +242,8 @@ namespace Flow.Launcher.Plugin.Explorer if (record.Type == ResultType.File && CanRunAsDifferentUser(record.FullPath)) contextMenus.Add(new Result { - Title = Context.API.GetTranslation("plugin_explorer_runasdifferentuser"), - SubTitle = Context.API.GetTranslation("plugin_explorer_runasdifferentuser_subtitle"), + Title = Localize.plugin_explorer_runasdifferentuser(), + SubTitle = Localize.plugin_explorer_runasdifferentuser_subtitle(), Action = (context) => { try @@ -259,8 +253,8 @@ namespace Flow.Launcher.Plugin.Explorer catch (FileNotFoundException e) { Context.API.ShowMsgError( - Context.API.GetTranslation("plugin_explorer_plugin_name"), - string.Format(Context.API.GetTranslation("plugin_explorer_file_not_found"), e.Message)); + Localize.plugin_explorer_plugin_name(), + Localize.plugin_explorer_file_not_found(e.Message)); return false; } @@ -317,8 +311,8 @@ namespace Flow.Launcher.Plugin.Explorer { return new Result { - Title = Context.API.GetTranslation("plugin_explorer_opencontainingfolder"), - SubTitle = Context.API.GetTranslation("plugin_explorer_opencontainingfolder_subtitle"), + Title = Localize.plugin_explorer_opencontainingfolder(), + SubTitle = Localize.plugin_explorer_opencontainingfolder_subtitle(), Action = _ => { try @@ -328,7 +322,7 @@ namespace Flow.Launcher.Plugin.Explorer catch (Exception e) { LogException($"Fail to open file at {record.FullPath}", e); - Context.API.ShowMsgError(string.Format(Context.API.GetTranslation("plugin_explorer_fail_to_open"), record.FullPath)); + Context.API.ShowMsgError(Localize.plugin_explorer_fail_to_open(record.FullPath)); return false; } @@ -339,11 +333,9 @@ namespace Flow.Launcher.Plugin.Explorer }; } - - private Result CreateOpenWithEditorResult(SearchResult record, string editorPath) { - var name = $"{Context.API.GetTranslation("plugin_explorer_openwitheditor")} {Path.GetFileNameWithoutExtension(editorPath)}"; + var name = $"{Localize.plugin_explorer_openwitheditor()} {Path.GetFileNameWithoutExtension(editorPath)}"; return new Result { @@ -361,8 +353,7 @@ namespace Flow.Launcher.Plugin.Explorer } catch (Exception e) { - var raw_message = Context.API.GetTranslation("plugin_explorer_openwitheditor_error"); - var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath); + var message = Localize.plugin_explorer_openwitheditor_error(record.FullPath, Path.GetFileNameWithoutExtension(editorPath), editorPath); LogException(message, e); Context.API.ShowMsgError(message); return false; @@ -377,7 +368,7 @@ namespace Flow.Launcher.Plugin.Explorer { string shellPath = Settings.ShellPath; - var name = $"{Context.API.GetTranslation("plugin_explorer_openwithshell")} {Path.GetFileNameWithoutExtension(shellPath)}"; + var name = $"{Localize.plugin_explorer_openwithshell()} {Path.GetFileNameWithoutExtension(shellPath)}"; return new Result { @@ -394,8 +385,7 @@ namespace Flow.Launcher.Plugin.Explorer } catch (Exception e) { - var raw_message = Context.API.GetTranslation("plugin_explorer_openwithshell_error"); - var message = string.Format(raw_message, record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath); + var message = Localize.plugin_explorer_openwithshell_error(record.FullPath, Path.GetFileNameWithoutExtension(shellPath), shellPath); LogException(message, e); Context.API.ShowMsgError(message); return false; @@ -410,8 +400,8 @@ namespace Flow.Launcher.Plugin.Explorer { return new Result { - Title = Context.API.GetTranslation("plugin_explorer_excludefromindexsearch"), - SubTitle = Context.API.GetTranslation("plugin_explorer_path") + " " + record.FullPath, + Title = Localize.plugin_explorer_excludefromindexsearch(), + SubTitle = Localize.plugin_explorer_path()+ " " + record.FullPath, Action = c_ => { if (!Settings.IndexSearchExcludedSubdirectoryPaths.Any(x => string.Equals(x.Path, record.FullPath, StringComparison.OrdinalIgnoreCase))) @@ -422,8 +412,8 @@ namespace Flow.Launcher.Plugin.Explorer _ = Task.Run(() => { - Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_excludedfromindexsearch_msg"), - Context.API.GetTranslation("plugin_explorer_path") + + Context.API.ShowMsg(Localize.plugin_explorer_excludedfromindexsearch_msg(), + Localize.plugin_explorer_path()+ " " + record.FullPath, Constants.ExplorerIconImageFullPath); // so the new path can be persisted to storage and not wait till next ViewModel save. @@ -441,8 +431,8 @@ namespace Flow.Launcher.Plugin.Explorer { return new Result { - Title = Context.API.GetTranslation("plugin_explorer_openindexingoptions"), - SubTitle = Context.API.GetTranslation("plugin_explorer_openindexingoptions_subtitle"), + Title = Localize.plugin_explorer_openindexingoptions(), + SubTitle = Localize.plugin_explorer_openindexingoptions_subtitle(), Action = _ => { try @@ -459,7 +449,7 @@ namespace Flow.Launcher.Plugin.Explorer } catch (Exception e) { - var message = Context.API.GetTranslation("plugin_explorer_openindexingoptions_errormsg"); + var message = Localize.plugin_explorer_openindexingoptions_errormsg(); LogException(message, e); Context.API.ShowMsgError(message); return false; @@ -470,12 +460,12 @@ namespace Flow.Launcher.Plugin.Explorer }; } - private Result CreateOpenWithMenu(SearchResult record) + private static Result CreateOpenWithMenu(SearchResult record) { return new Result { - Title = Context.API.GetTranslation("plugin_explorer_openwith"), - SubTitle = Context.API.GetTranslation("plugin_explorer_openwith_subtitle"), + Title = Localize.plugin_explorer_openwith(), + SubTitle = Localize.plugin_explorer_openwith_subtitle(), Action = _ => { Process.Start("rundll32.exe", $"{Path.Combine(Environment.SystemDirectory, "shell32.dll")},OpenAs_RunDLL {record.FullPath}"); 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 af33f4da2..a837a49b4 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -19,6 +19,7 @@ ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Explorer + $(NoWarn);FLSG0007 @@ -47,8 +48,8 @@ - - + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 16ef037cc..c40040df5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -24,6 +24,7 @@ Error occurred during search: {0} Could not open folder Could not open file + This new action keyword is already assigned to another plugin, please choose a different one Delete diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index d93c6c77b..f5b8b9325 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -90,12 +90,12 @@ namespace Flow.Launcher.Plugin.Explorer public string GetTranslatedPluginTitle() { - return Context.API.GetTranslation("plugin_explorer_plugin_name"); + return Localize.plugin_explorer_plugin_name(); } public string GetTranslatedPluginDescription() { - return Context.API.GetTranslation("plugin_explorer_plugin_description"); + return Localize.plugin_explorer_plugin_description(); } public void OnCultureInfoChanged(CultureInfo newCulture) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs index c8bd68279..13d988f1a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingDownloadHelper.cs @@ -21,9 +21,9 @@ public static class EverythingDownloadHelper if (string.IsNullOrEmpty(installedLocation)) { if (api.ShowMsgBox( - string.Format(api.GetTranslation("flowlauncher_plugin_everything_installing_select"), Environment.NewLine), - api.GetTranslation("flowlauncher_plugin_everything_installing_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) + Localize.flowlauncher_plugin_everything_installing_select(Environment.NewLine), + Localize.flowlauncher_plugin_everything_installing_title(), + MessageBoxButton.YesNo) == MessageBoxResult.Yes) { var dlg = new System.Windows.Forms.OpenFileDialog { @@ -41,13 +41,13 @@ public static class EverythingDownloadHelper return installedLocation; } - api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"), - api.GetTranslation("flowlauncher_plugin_everything_installing_subtitle"), "", useMainWindowAsOwner: false); + api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(), + Localize.flowlauncher_plugin_everything_installing_subtitle(), "", useMainWindowAsOwner: false); await DroplexPackage.Drop(App.Everything1_4_1_1009).ConfigureAwait(false); - api.ShowMsg(api.GetTranslation("flowlauncher_plugin_everything_installing_title"), - api.GetTranslation("flowlauncher_plugin_everything_installationsuccess_subtitle"), "", useMainWindowAsOwner: false); + api.ShowMsg(Localize.flowlauncher_plugin_everything_installing_title(), + Localize.flowlauncher_plugin_everything_installationsuccess_subtitle(), "", useMainWindowAsOwner: false); installedLocation = "C:\\Program Files\\Everything\\Everything.exe"; @@ -83,6 +83,5 @@ public static class EverythingDownloadHelper var scoopInstalledPath = Environment.ExpandEnvironmentVariables(@"%userprofile%\scoop\apps\everything\current\Everything.exe"); return File.Exists(scoopInstalledPath) ? scoopInstalledPath : string.Empty; - } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs index ce71c94ba..eb994a6f9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/EverythingSearchManager.cs @@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything if (!await EverythingApi.IsEverythingRunningAsync(token)) throw new EngineNotAvailableException( Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"), - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"), + Localize.flowlauncher_plugin_everything_click_to_launch_or_install(), + Localize.flowlauncher_plugin_everything_is_not_running(), Constants.EverythingErrorImagePath, ClickToInstallEverythingAsync); } @@ -38,7 +38,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, "Please check whether your system is x86 or x64", Constants.GeneralSearchErrorImagePath, - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue")); + Localize.flowlauncher_plugin_everything_sdk_issue()); } } @@ -50,7 +50,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything if (installedPath == null) { - Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_not_found")); + Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_not_found()); Main.Context.API.LogError(ClassName, "Unable to find Everything.exe"); return false; @@ -65,7 +65,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything // Just let the user know that Everything is not installed properly and ask them to install it manually catch (Exception e) { - Main.Context.API.ShowMsgError(Main.Context.API.GetTranslation("flowlauncher_plugin_everything_install_issue")); + Main.Context.API.ShowMsgError(Localize.flowlauncher_plugin_everything_install_issue()); Main.Context.API.LogException(ClassName, "Failed to install Everything", e); return false; @@ -97,8 +97,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything if (!Settings.EnableEverythingContentSearch) { throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!, - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"), - Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"), + Localize.flowlauncher_plugin_everything_enable_content_search(), + Localize.flowlauncher_plugin_everything_enable_content_search_tips(), Constants.EverythingErrorImagePath, _ => { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index dfa2c8d43..18eb168b9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -124,7 +124,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } catch (Exception ex) { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error()); return false; } } @@ -138,7 +138,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } catch (Exception ex) { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error()); return false; } } @@ -153,7 +153,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search } catch (Exception ex) { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_opendir_error")); + Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_opendir_error()); return false; } } @@ -166,7 +166,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search return false; }, Score = score, - TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenDirectory"), + TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenDirectory(), SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFolderMoreInfoTooltip(path) : path, ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path, WindowsIndexed = windowsIndexed } }; @@ -190,7 +190,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search DriveInfo drv = new DriveInfo(driveLetter); var freespace = ToReadableSize(drv.AvailableFreeSpace, 2); var totalspace = ToReadableSize(drv.TotalSize, 2); - var subtitle = string.Format(Context.API.GetTranslation("plugin_explorer_diskfreespace"), freespace, totalspace); + var subtitle = Localize.plugin_explorer_diskfreespace(freespace, totalspace); double usingSize = (Convert.ToDouble(drv.TotalSize) - Convert.ToDouble(drv.AvailableFreeSpace)) / Convert.ToDouble(drv.TotalSize) * 100; int? progressValue = Convert.ToInt32(usingSize); @@ -262,8 +262,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search return new Result { - Title = Context.API.GetTranslation("plugin_explorer_openresultfolder"), - SubTitle = Context.API.GetTranslation("plugin_explorer_openresultfolder_subtitle"), + Title = Localize.plugin_explorer_openresultfolder(), + SubTitle = Localize.plugin_explorer_openresultfolder_subtitle(), AutoCompleteText = GetPathWithActionKeyword(folderPath, ResultType.Folder, actionKeyword), IcoPath = folderPath, Score = 500, @@ -330,12 +330,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search } catch (Exception ex) { - Context.API.ShowMsgBox(ex.Message, Context.API.GetTranslation("plugin_explorer_openfile_error")); + Context.API.ShowMsgBox(ex.Message, Localize.plugin_explorer_openfile_error()); } return true; }, - TitleToolTip = Main.Context.API.GetTranslation("plugin_explorer_plugin_ToolTipOpenContainingFolder"), + TitleToolTip = Localize.plugin_explorer_plugin_ToolTipOpenContainingFolder(), SubTitleToolTip = Settings.DisplayMoreInformationInToolTip ? GetFileMoreInfoTooltip(filePath) : filePath, ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath, WindowsIndexed = windowsIndexed } }; @@ -374,8 +374,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var fileSize = PreviewPanel.GetFileSize(filePath); var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); - return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"), - filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine); + return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine); } catch (Exception e) { @@ -391,8 +390,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search var folderSize = PreviewPanel.GetFolderSize(folderPath); var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); - return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info"), - folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine); + return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine); } catch (Exception e) { @@ -403,8 +401,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search private static string GetVolumeMoreInfoTooltip(string volumePath, string freespace, string totalspace) { - return string.Format(Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_volume"), - volumePath, freespace, totalspace, Environment.NewLine); + return Localize.plugin_explorer_plugin_tooltip_more_info_volume(volumePath, freespace, totalspace, Environment.NewLine); } private static readonly string[] MediaExtensions = diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index f4f87d4d4..f9d8963e6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -161,8 +161,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search { new() { - Title = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"), - SubTitle = Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"), + Title = Localize.flowlauncher_plugin_everything_enable_content_search(), + SubTitle = Localize.flowlauncher_plugin_everything_enable_content_search_tips(), IcoPath = "Images/index_error.png", Action = c => { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs index 3d69a1ee6..eeb5c2c4a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/WindowsIndexSearchManager.cs @@ -105,8 +105,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex throw new EngineNotAvailableException( "Windows Index", - Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceFix"), - Main.Context.API.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"), + Localize.plugin_explorer_windowsSearchServiceFix(), + Localize.plugin_explorer_windowsSearchServiceNotRunning(), Constants.WindowsIndexErrorImagePath, c => { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 672e81d03..8d62531cd 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -14,9 +14,9 @@ namespace Flow.Launcher.Plugin.Explorer { public int MaxResult { get; set; } = 100; - public ObservableCollection QuickAccessLinks { get; set; } = new(); + public ObservableCollection QuickAccessLinks { get; set; } = []; - public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = new ObservableCollection(); + public ObservableCollection IndexSearchExcludedSubdirectoryPaths { get; set; } = []; public string EditorPath { get; set; } = ""; @@ -58,7 +58,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool QuickAccessKeywordEnabled { get; set; } - public bool WarnWindowsSearchServiceOff { get; set; } = true; public bool ShowFileSizeInPreviewPanel { get; set; } = true; @@ -69,7 +68,6 @@ namespace Flow.Launcher.Plugin.Explorer public bool ShowFileAgeInPreviewPanel { get; set; } = false; - public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd"; public string PreviewPanelTimeFormat { get; set; } = "HH:mm"; @@ -82,8 +80,8 @@ namespace Flow.Launcher.Plugin.Explorer private EverythingSearchManager EverythingManagerInstance => _everythingManagerInstance ??= new EverythingSearchManager(this); private WindowsIndexSearchManager WindowsIndexSearchManager => _windowsIndexSearchManager ??= new WindowsIndexSearchManager(this); - public IndexSearchEngineOption IndexSearchEngine { get; set; } = IndexSearchEngineOption.WindowsIndex; + [JsonIgnore] public IIndexProvider IndexProvider => IndexSearchEngine switch { @@ -139,7 +137,6 @@ namespace Flow.Launcher.Plugin.Explorer #endregion - #region Everything Settings public string EverythingInstalledPath { get; set; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 7292697ce..2d46c6307 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -296,7 +296,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels return; } - var actionKeywordWindow = new ActionKeywordSetting(actionKeyword, Context.API); + var actionKeywordWindow = new ActionKeywordSetting(actionKeyword); if (!(actionKeywordWindow.ShowDialog() ?? false)) { @@ -432,8 +432,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels case "QuickAccessLink": if (SelectedQuickAccessLink == null) return; if (Context.API.ShowMsgBox( - Context.API.GetTranslation("plugin_explorer_delete_quick_access_link"), - Context.API.GetTranslation("plugin_explorer_delete"), + Localize.plugin_explorer_delete_quick_access_link(), + Localize.plugin_explorer_delete(), MessageBoxButton.OKCancel, MessageBoxImage.Warning) == MessageBoxResult.Cancel) @@ -443,8 +443,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels case "IndexSearchExcludedPaths": if (SelectedIndexSearchExcludedPath == null) return; if (Context.API.ShowMsgBox( - Context.API.GetTranslation("plugin_explorer_delete_index_search_excluded_path"), - Context.API.GetTranslation("plugin_explorer_delete"), + Localize.plugin_explorer_delete_index_search_excluded_path(), + Localize.plugin_explorer_delete(), MessageBoxButton.OKCancel, MessageBoxImage.Warning) == MessageBoxResult.Cancel) @@ -457,7 +457,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels private void ShowUnselectedMessage() { - var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); + var warning = Localize.plugin_explorer_make_selection_warning(); Context.API.ShowMsgBox(warning); } @@ -577,8 +577,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } } - public int MaxResultLowerLimit => 1; - public int MaxResultUpperLimit => 100000; + public int MaxResultLowerLimit { get; } = 1; + public int MaxResultUpperLimit { get; } = 100000; public int MaxResult { @@ -592,7 +592,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels #region Everything FastSortWarning - public List AllEverythingSortOptions = EverythingSortOptionLocalized.GetValues(); + public List AllEverythingSortOptions { get; } = EverythingSortOptionLocalized.GetValues(); public EverythingSortOption SelectedEverythingSortOption { diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs index 829a2feed..562170062 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs @@ -29,13 +29,11 @@ namespace Flow.Launcher.Plugin.Explorer.Views } private string actionKeyword; - private readonly IPublicAPI _api; private bool _keywordEnabled; - public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword, IPublicAPI api) + public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword) { CurrentActionKeyword = selectedActionKeyword; - _api = api; ActionKeyword = selectedActionKeyword.Keyword; KeywordEnabled = selectedActionKeyword.Enabled; @@ -60,14 +58,14 @@ namespace Flow.Launcher.Plugin.Explorer.Views switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled) { case (Settings.ActionKeyword.FileContentSearchActionKeyword, true): - _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_globalActionKeywordInvalid")); + Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid()); return; case (Settings.ActionKeyword.QuickAccessActionKeyword, true): - _api.ShowMsgBox(_api.GetTranslation("plugin_explorer_quickaccess_globalActionKeywordInvalid")); + Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid()); return; } - if (!KeywordEnabled || !_api.ActionKeywordAssigned(ActionKeyword)) + if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword)) { DialogResult = true; Close(); @@ -75,7 +73,7 @@ namespace Flow.Launcher.Plugin.Explorer.Views } // The keyword is not valid, so show message - _api.ShowMsgBox(_api.GetTranslation("newActionKeywordsHasBeenAssigned")); + Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned()); } private void BtnCancel_OnClick(object sender, RoutedEventArgs e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs index 4dd0588ee..3c627cc06 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs @@ -25,7 +25,7 @@ public partial class PreviewPanel : UserControl public string FileName { get; } [ObservableProperty] - private string _fileSize = Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + private string _fileSize = Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); [ObservableProperty] private string _createdAt = ""; @@ -111,17 +111,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"File not found: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get file size for {filePath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -142,17 +142,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"File not found: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get file created date for {filePath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -173,17 +173,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"File not found: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to file: {filePath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get file modified date for {filePath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -205,17 +205,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (OperationCanceledException) { Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } // For parallel operations, AggregateException may be thrown if any of the tasks fail catch (AggregateException ae) @@ -224,22 +224,22 @@ public partial class PreviewPanel : UserControl { case FileNotFoundException: Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); case UnauthorizedAccessException: Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); case OperationCanceledException: Main.Context.API.LogError(ClassName, $"Operation timed out while calculating folder size for {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); default: Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", ae); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get folder size for {folderPath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -260,17 +260,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get folder created date for {folderPath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -291,17 +291,17 @@ public partial class PreviewPanel : UserControl catch (FileNotFoundException) { Main.Context.API.LogError(ClassName, $"Folder not found: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (UnauthorizedAccessException) { Main.Context.API.LogError(ClassName, $"Access denied to folder: {folderPath}"); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } catch (Exception e) { Main.Context.API.LogException(ClassName, $"Failed to get folder modified date for {folderPath}", e); - return Main.Context.API.GetTranslation("plugin_explorer_plugin_tooltip_more_info_unknown"); + return Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); } } @@ -311,21 +311,20 @@ public partial class PreviewPanel : UserControl var difference = now - fileDateTime; if (difference.TotalDays < 1) - return Main.Context.API.GetTranslation("Today"); + return Localize.Today(); if (difference.TotalDays < 30) - return string.Format(Main.Context.API.GetTranslation("DaysAgo"), (int)difference.TotalDays); + return Localize.DaysAgo((int)difference.TotalDays); var monthsDiff = (now.Year - fileDateTime.Year) * 12 + now.Month - fileDateTime.Month; if (monthsDiff == 1) - return Main.Context.API.GetTranslation("OneMonthAgo"); + return Localize.OneMonthAgo(); if (monthsDiff < 12) - return string.Format(Main.Context.API.GetTranslation("MonthsAgo"), monthsDiff); + return Localize.MonthsAgo(monthsDiff); var yearsDiff = now.Year - fileDateTime.Year; if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day)) yearsDiff--; - return yearsDiff == 1 ? Main.Context.API.GetTranslation("OneYearAgo") : - string.Format(Main.Context.API.GetTranslation("YearsAgo"), yearsDiff); + return yearsDiff == 1 ? Localize.OneYearAgo(): Localize.YearsAgo(yearsDiff); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs index e6294b98b..f8929549b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs @@ -97,7 +97,7 @@ public partial class QuickAccessLinkSettings // Validate the input before proceeding if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath)) { - var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected"); + var warning = Localize.plugin_explorer_quick_access_link_no_folder_selected(); Main.Context.API.ShowMsgBox(warning); return; } @@ -107,7 +107,7 @@ public partial class QuickAccessLinkSettings x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) && x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase))) { - var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists"); + var warning = Localize.plugin_explorer_quick_access_link_path_already_exists(); Main.Context.API.ShowMsgBox(warning); return; } diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 2da97ebbd..0a7a02a45 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -52,7 +52,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index da2b19d7c..e9515fab4 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -64,12 +64,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index 7c30c0c96..0258a10d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -31,7 +31,7 @@ namespace Flow.Launcher.Plugin.Program internal static PluginInitContext Context { get; private set; } - private static readonly List emptyResults = new(); + private static readonly List emptyResults = []; private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; private static MemoryCache cache = new(cacheOptions); @@ -84,7 +84,6 @@ namespace Flow.Launcher.Plugin.Program { await _win32sLock.WaitAsync(token); await _uwpsLock.WaitAsync(token); - try { // Collect all UWP Windows app directories @@ -117,7 +116,7 @@ namespace Flow.Launcher.Plugin.Program } }, token); - resultList = resultList.Any() ? resultList : emptyResults; + resultList = resultList.Count != 0 ? resultList : emptyResults; entry.SetSize(resultList.Count); entry.SetSlidingExpiration(TimeSpan.FromHours(8)); @@ -250,14 +249,26 @@ namespace Flow.Launcher.Plugin.Program } await _win32sLock.WaitAsync(); - _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List()); - _win32sCount = _win32s.Count; - _win32sLock.Release(); + try + { + _win32s = await context.API.LoadCacheBinaryStorageAsync(Win32CacheName, pluginCacheDirectory, new List()); + _win32sCount = _win32s.Count; + } + finally + { + _win32sLock.Release(); + } await _uwpsLock.WaitAsync(); - _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List()); - _uwpsCount = _uwps.Count; - _uwpsLock.Release(); + try + { + _uwps = await context.API.LoadCacheBinaryStorageAsync(UwpCacheName, pluginCacheDirectory, new List()); + _uwpsCount = _uwps.Count; + } + finally + { + _uwpsLock.Release(); + } }); Context.API.LogInfo(ClassName, $"Number of preload win32 programs <{_win32sCount}>"); Context.API.LogInfo(ClassName, $"Number of preload uwps <{_uwpsCount}>"); @@ -408,38 +419,46 @@ namespace Flow.Launcher.Plugin.Program return; await _uwpsLock.WaitAsync(); - if (_uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) + var reindexUwps = true; + try { + reindexUwps = _uwps.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); var program = _uwps.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); program.Enabled = false; _settings.DisabledProgramSources.Add(new ProgramSource(program)); + } + finally + { _uwpsLock.Release(); + } - // Reindex UWP programs + // Reindex UWP programs + if (reindexUwps) + { _ = Task.Run(IndexUwpProgramsAsync); return; } - else - { - _uwpsLock.Release(); - } await _win32sLock.WaitAsync(); - if (_win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier)) + var reindexWin32s = true; + try { + reindexWin32s = _win32s.Any(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); var program = _win32s.First(x => x.UniqueIdentifier == programToDelete.UniqueIdentifier); program.Enabled = false; _settings.DisabledProgramSources.Add(new ProgramSource(program)); - _win32sLock.Release(); - - // Reindex Win32 programs - _ = Task.Run(IndexWin32ProgramsAsync); - return; } - else + finally { _win32sLock.Release(); } + + // Reindex Win32 programs + if (reindexWin32s) + { + _ = Task.Run(IndexWin32ProgramsAsync); + return; + } } public static void StartProcess(Func runProcess, ProcessStartInfo info) diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs index b89a2a6ba..2a6a3e987 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/Commands/ProgramSettingDisplay.cs @@ -19,18 +19,30 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands internal static async Task DisplayAllProgramsAsync() { await Main._win32sLock.WaitAsync(); - var win32 = Main._win32s + try + { + var win32 = Main._win32s .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Select(x => new ProgramSource(x)); - ProgramSetting.ProgramSettingDisplayList.AddRange(win32); - Main._win32sLock.Release(); + ProgramSetting.ProgramSettingDisplayList.AddRange(win32); + } + finally + { + Main._win32sLock.Release(); + } await Main._uwpsLock.WaitAsync(); - var uwp = Main._uwps + try + { + var uwp = Main._uwps .Where(t1 => !ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == t1.UniqueIdentifier)) .Select(x => new ProgramSource(x)); - ProgramSetting.ProgramSettingDisplayList.AddRange(uwp); - Main._uwpsLock.Release(); + ProgramSetting.ProgramSettingDisplayList.AddRange(uwp); + } + finally + { + Main._uwpsLock.Release(); + } } internal static async Task SetProgramSourcesStatusAsync(List selectedProgramSourcesToDisable, bool status) @@ -44,24 +56,36 @@ namespace Flow.Launcher.Plugin.Program.Views.Commands } await Main._win32sLock.WaitAsync(); - foreach (var program in Main._win32s) + try { - if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) + foreach (var program in Main._win32s) { - program.Enabled = status; + if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) + { + program.Enabled = status; + } } } - Main._win32sLock.Release(); + finally + { + Main._win32sLock.Release(); + } await Main._uwpsLock.WaitAsync(); - foreach (var program in Main._uwps) + try { - if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) + foreach (var program in Main._uwps) { - program.Enabled = status; + if (selectedProgramSourcesToDisable.Any(x => x.UniqueIdentifier == program.UniqueIdentifier && program.Enabled != status)) + { + program.Enabled = status; + } } } - Main._uwpsLock.Release(); + finally + { + Main._uwpsLock.Release(); + } } internal static void StoreDisabledInSettings() diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index 8e54e1894..44fc9a8cf 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -58,7 +58,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive