From 9ce911771e073011c9526f9700ccded412087ef4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 11:31:54 +0800 Subject: [PATCH 001/125] Improve code quality --- Flow.Launcher/App.xaml.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 6e053db29..d002ba178 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -181,12 +181,14 @@ namespace Flow.Launcher // So set to OnExplicitShutdown to prevent the application from shutting down before main window is created Current.ShutdownMode = ShutdownMode.OnExplicitShutdown; + // Setup log level before any logging is done Log.SetLogLevel(_settings.LogLevel); // Update dynamic resources base on settings Current.Resources["SettingWindowFont"] = new FontFamily(_settings.SettingWindowFont); Current.Resources["ContentControlThemeFontFamily"] = new FontFamily(_settings.SettingWindowFont); + // Initialize notification system before any notification api is called Notification.Install(); // Enable Win32 dark mode if the system is in dark mode before creating all windows @@ -195,6 +197,7 @@ namespace Flow.Launcher // Initialize language before portable clean up since it needs translations await Ioc.Default.GetRequiredService().InitializeLanguageAsync(); + // Clean up after portability update Ioc.Default.GetRequiredService().PreStartCleanUpAfterPortabilityUpdate(); API.LogInfo(ClassName, "Begin Flow Launcher startup ----------------------------------------------------"); From aee46e864057ca1176f6aac8aec08c550c34c790 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 12:10:06 +0800 Subject: [PATCH 002/125] Initialize quick jump earlier --- Flow.Launcher/App.xaml.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index d002ba178..33d6c724c 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -233,6 +233,11 @@ namespace Flow.Launcher Current.MainWindow = _mainWindow; Current.MainWindow.Title = Constant.FlowLauncher; + // Initialize quick jump before hotkey mapper since hotkey mapper will register quick jump hotkey + // Initialize quick jump after main window is created so that it can access main window handle + DialogJump.InitializeDialogJump(PluginManager.GetDialogJumpExplorers(), PluginManager.GetDialogJumpDialogs()); + DialogJump.SetupDialogJump(_settings.EnableDialogJump); + // Initialize hotkey mapper instantly after main window is created because // it will steal focus from main window which causes window hide HotKeyMapper.Initialize(); @@ -240,9 +245,6 @@ namespace Flow.Launcher // Initialize theme for main window Ioc.Default.GetRequiredService().ChangeTheme(); - DialogJump.InitializeDialogJump(PluginManager.GetDialogJumpExplorers(), PluginManager.GetDialogJumpDialogs()); - DialogJump.SetupDialogJump(_settings.EnableDialogJump); - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); RegisterExitEvents(); From f5f256809c057bea58840c3baf93baaba39ac0e2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 13:52:11 +0800 Subject: [PATCH 003/125] Add IResultUpdateRegister interface --- .../Plugin/IResultUpdateRegister.cs | 12 +++ Flow.Launcher/ViewModel/MainViewModel.cs | 74 +++++++++---------- 2 files changed, 48 insertions(+), 38 deletions(-) create mode 100644 Flow.Launcher.Core/Plugin/IResultUpdateRegister.cs diff --git a/Flow.Launcher.Core/Plugin/IResultUpdateRegister.cs b/Flow.Launcher.Core/Plugin/IResultUpdateRegister.cs new file mode 100644 index 000000000..1da04bf01 --- /dev/null +++ b/Flow.Launcher.Core/Plugin/IResultUpdateRegister.cs @@ -0,0 +1,12 @@ +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin; + +public interface IResultUpdateRegister +{ + /// + /// Register a plugin to receive results updated event. + /// + /// + void RegisterResultsUpdatedEvent(PluginPair pair); +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 5eae23c27..5816f1a85 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -27,7 +27,7 @@ using ModernWpf; namespace Flow.Launcher.ViewModel { - public partial class MainViewModel : BaseModel, ISavable, IDisposable + public partial class MainViewModel : BaseModel, ISavable, IDisposable, IResultUpdateRegister { #region Private Fields @@ -274,52 +274,50 @@ namespace Flow.Launcher.ViewModel } } - public void RegisterResultsUpdatedEvent() + public void RegisterResultsUpdatedEvent(PluginPair pair) { - foreach (var pair in PluginManager.GetResultUpdatePlugin()) + if (pair.Plugin is not IResultUpdated plugin) return; + + plugin.ResultsUpdated += (s, e) => { - var plugin = (IResultUpdated)pair.Plugin; - plugin.ResultsUpdated += (s, e) => + if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested) { - if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested) + return; + } + + var token = e.Token == default ? _updateToken : e.Token; + + IReadOnlyList resultsCopy; + if (e.Results == null) + { + resultsCopy = _emptyResult; + } + else + { + // make a clone to avoid possible issue that plugin will also change the list and items when updating view model + resultsCopy = DeepCloneResults(e.Results, false, token); + } + + foreach (var result in resultsCopy) + { + if (string.IsNullOrEmpty(result.BadgeIcoPath)) { - return; + result.BadgeIcoPath = pair.Metadata.IcoPath; } + } - var token = e.Token == default ? _updateToken : e.Token; + PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query); - IReadOnlyList resultsCopy; - if (e.Results == null) - { - resultsCopy = _emptyResult; - } - else - { - // make a clone to avoid possible issue that plugin will also change the list and items when updating view model - resultsCopy = DeepCloneResults(e.Results, false, token); - } + if (token.IsCancellationRequested) return; - foreach (var result in resultsCopy) - { - if (string.IsNullOrEmpty(result.BadgeIcoPath)) - { - result.BadgeIcoPath = pair.Metadata.IcoPath; - } - } + App.API.LogDebug(ClassName, $"Update results for plugin <{pair.Metadata.Name}>"); - PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query); - - if (token.IsCancellationRequested) return; - - App.API.LogDebug(ClassName, $"Update results for plugin <{pair.Metadata.Name}>"); - - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, - token))) - { - App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); - } - }; - } + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, + token))) + { + App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); + } + }; } private async Task RegisterClockAndDateUpdateAsync() From d4e672d630f035a7157323db40fda72d2768ee15 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 13:55:47 +0800 Subject: [PATCH 004/125] Add support to update translation for one plugin --- .../Resource/Internationalization.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index d2ab2d028..37e4966de 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -367,6 +367,22 @@ namespace Flow.Launcher.Core.Resource } } + public static void UpdatePluginMetadataTranslation(PluginPair p) + { + // Update plugin metadata name & description + if (p.Plugin is not IPluginI18n pluginI18N) return; + try + { + p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); + p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); + } + catch (Exception e) + { + API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); + } + } + #endregion } } From c2157e2df1625f8a86d7b22d881cfd4a0e1f8640 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 14:03:45 +0800 Subject: [PATCH 005/125] Add support for concurrent operation of explorers & dialogs adding --- .../DialogJump/DialogJump.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 65652878f..756aa89c1 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -13,6 +13,7 @@ using NHotkey; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.Accessibility; +using System.Collections.Concurrent; namespace Flow.Launcher.Infrastructure.DialogJump { @@ -64,12 +65,12 @@ namespace Flow.Launcher.Infrastructure.DialogJump private static HWND _mainWindowHandle = HWND.Null; - private static readonly Dictionary _dialogJumpExplorers = new(); + private static readonly ConcurrentDictionary _dialogJumpExplorers = new(); private static DialogJumpExplorerPair _lastExplorer = null; private static readonly object _lastExplorerLock = new(); - private static readonly Dictionary _dialogJumpDialogs = new(); + private static readonly ConcurrentDictionary _dialogJumpDialogs = new(); private static IDialogJumpDialogWindow _dialogWindow = null; private static readonly object _dialogWindowLock = new(); @@ -105,22 +106,13 @@ namespace Flow.Launcher.Infrastructure.DialogJump #region Initialize & Setup - public static void InitializeDialogJump(IList dialogJumpExplorers, - IList dialogJumpDialogs) + public static void InitializeDialogJump() { if (_initialized) return; - // Initialize Dialog Jump explorers & dialogs - _dialogJumpExplorers.Add(WindowsDialogJumpExplorer, null); - foreach (var explorer in dialogJumpExplorers) - { - _dialogJumpExplorers.Add(explorer, null); - } - _dialogJumpDialogs.Add(WindowsDialogJumpDialog, null); - foreach (var dialog in dialogJumpDialogs) - { - _dialogJumpDialogs.Add(dialog, null); - } + // Initialize preinstalled Dialog Jump explorers & dialogs + _dialogJumpExplorers.TryAdd(WindowsDialogJumpExplorer, null); + _dialogJumpDialogs.TryAdd(WindowsDialogJumpDialog, null); // Initialize main window handle _mainWindowHandle = Win32Helper.GetMainWindowHandle(); @@ -135,6 +127,29 @@ namespace Flow.Launcher.Infrastructure.DialogJump _initialized = true; } + public static void InitializeDialogJumpPlugin(PluginPair pair) + { + // Add Dialog Jump explorers & dialogs + if (pair.Plugin is IDialogJumpExplorer explorer) + { + var dialogJumpExplorer = new DialogJumpExplorerPair + { + Plugin = explorer, + Metadata = pair.Metadata + }; + _dialogJumpExplorers.TryAdd(dialogJumpExplorer, null); + } + else if (pair.Plugin is IDialogJumpDialog dialog) + { + var dialogJumpDialog = new DialogJumpDialogPair + { + Plugin = dialog, + Metadata = pair.Metadata + }; + _dialogJumpDialogs.TryAdd(dialogJumpDialog, null); + } + } + public static void SetupDialogJump(bool enabled) { if (enabled == _enabled) return; From 6beeecb0f740f98652271f083fb7826d8d37510a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 14:26:24 +0800 Subject: [PATCH 006/125] Use async load & initialization model --- Flow.Launcher.Core/Plugin/PluginManager.cs | 206 ++++++++++-------- Flow.Launcher/App.xaml.cs | 35 +-- Flow.Launcher/MainWindow.xaml.cs | 4 +- Flow.Launcher/PublicAPIInstance.cs | 2 +- .../SettingsPanePluginsViewModel.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 6 +- 6 files changed, 140 insertions(+), 115 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index a4ab8de08..51530705e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Core.Resource; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; using Flow.Launcher.Infrastructure.UserSettings; @@ -25,24 +26,21 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginManager); - public static List AllPlugins { get; private set; } - public static readonly HashSet GlobalPlugins = new(); - public static readonly Dictionary NonGlobalPlugins = new(); - // 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(); + private static readonly ConcurrentDictionary _allPlugins = []; + private static readonly ConcurrentDictionary _globalPlugins = []; + private static readonly ConcurrentDictionary _nonGlobalPlugins = []; + private static PluginsSettings Settings; - private static readonly ConcurrentBag ModifiedPlugins = new(); + private static readonly ConcurrentBag ModifiedPlugins = []; - private static IEnumerable _contextMenuPlugins; - private static IEnumerable _homePlugins; - private static IEnumerable _resultUpdatePlugin; - private static IEnumerable _translationPlugins; - - private static readonly List _dialogJumpExplorerPlugins = new(); - private static readonly List _dialogJumpDialogPlugins = new(); + private static readonly ConcurrentBag _contextMenuPlugins = []; + private static readonly ConcurrentBag _homePlugins = []; + private static readonly ConcurrentBag _translationPlugins = []; + private static readonly ConcurrentBag _externalPreviewPlugins = []; /// /// Directories that will hold Flow Launcher plugin directory @@ -61,12 +59,22 @@ namespace Flow.Launcher.Core.Plugin } } + public static List GetAllPlugins() + { + return [.. _allPlugins.Values]; + } + + public static Dictionary GetNonGlobalPlugins() + { + return _nonGlobalPlugins.ToDictionary(); + } + /// /// Save json and ISavable /// public static void Save() { - foreach (var pluginPair in AllPlugins) + foreach (var pluginPair in GetAllPlugins()) { var savable = pluginPair.Plugin as ISavable; try @@ -85,7 +93,7 @@ namespace Flow.Launcher.Core.Plugin public static async ValueTask DisposePluginsAsync() { - foreach (var pluginPair in AllPlugins) + foreach (var pluginPair in GetAllPlugins()) { await DisposePluginAsync(pluginPair); } @@ -113,7 +121,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadDataAsync() { - await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch { IReloadable p => Task.Run(p.ReloadData), IAsyncReloadable p => p.ReloadDataAsync(), @@ -123,7 +131,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -132,7 +140,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task CloseExternalPreviewAsync() { - await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.ClosePreviewAsync(), _ => Task.CompletedTask, @@ -141,7 +149,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -150,12 +158,12 @@ namespace Flow.Launcher.Core.Plugin public static bool UseExternalPreview() { - return GetPluginsForInterface().Any(x => !x.Metadata.Disabled); + return GetExternalPreviewPlugins().Any(x => !x.Metadata.Disabled); } public static bool AllowAlwaysPreview() { - var plugin = GetPluginsForInterface().FirstOrDefault(x => !x.Metadata.Disabled); + var plugin = GetExternalPreviewPlugins().FirstOrDefault(x => !x.Metadata.Disabled); if (plugin is null) return false; @@ -176,38 +184,19 @@ namespace Flow.Launcher.Core.Plugin /// todo happlebao The API should be removed /// /// - public static void LoadPlugins(PluginsSettings settings) + public static List LoadPlugins(PluginsSettings settings) { var metadatas = PluginConfig.Parse(Directories); Settings = settings; Settings.UpdatePluginSettings(metadatas); - AllPlugins = PluginsLoader.Plugins(metadatas, Settings); + + // Load plugins + var allPlugins = PluginsLoader.Plugins(metadatas, Settings); + // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins UpdatePluginDirectory(metadatas); - // Initialize plugin enumerable after all plugins are initialized - _contextMenuPlugins = GetPluginsForInterface(); - _homePlugins = GetPluginsForInterface(); - _resultUpdatePlugin = GetPluginsForInterface(); - _translationPlugins = GetPluginsForInterface(); - - // Initialize Dialog Jump plugin pairs - foreach (var pair in GetPluginsForInterface()) - { - _dialogJumpExplorerPlugins.Add(new DialogJumpExplorerPair - { - Plugin = (IDialogJumpExplorer)pair.Plugin, - Metadata = pair.Metadata - }); - } - foreach (var pair in GetPluginsForInterface()) - { - _dialogJumpDialogPlugins.Add(new DialogJumpDialogPair - { - Plugin = (IDialogJumpDialog)pair.Plugin, - Metadata = pair.Metadata - }); - } + return allPlugins; } private static void UpdatePluginDirectory(List metadatas) @@ -241,11 +230,11 @@ namespace Flow.Launcher.Core.Plugin /// Call initialize for all plugins /// /// return the list of failed to init plugins or null for none - public static async Task InitializePluginsAsync() + public static async Task InitializePluginsAsync(List allPlugins, IResultUpdateRegister register) { var failedPlugins = new ConcurrentQueue(); - var InitTasks = AllPlugins.Select(pair => Task.Run(async delegate + var InitTasks = allPlugins.Select(pair => Task.Run(async delegate { try { @@ -255,6 +244,9 @@ namespace Flow.Launcher.Core.Plugin pair.Metadata.InitTime += milliseconds; API.LogInfo(ClassName, $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); + + // Add it in all plugin list + _allPlugins.TryAdd(pair.Metadata.ID, pair); } catch (Exception e) { @@ -272,30 +264,59 @@ namespace Flow.Launcher.Core.Plugin failedPlugins.Enqueue(pair); API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } + + // Even if the plugin cannot be initialized, we still need to add it in all plugin list + _allPlugins.TryAdd(pair.Metadata.ID, pair); + return; } - })); - await Task.WhenAll(InitTasks); + // Initialize plugin lists after the plugin is initialized + if (pair.Plugin is IContextMenu) + { + _contextMenuPlugins.Add(pair); + } + if (pair.Plugin is IAsyncHomeQuery) + { + _homePlugins.Add(pair); + } + if (pair.Plugin is IPluginI18n) + { + _translationPlugins.Add(pair); + } + if (pair.Plugin is IAsyncExternalPreview) + { + _externalPreviewPlugins.Add(pair); + } - foreach (var plugin in AllPlugins) - { + // Register ResultsUpdated event so that plugin query can use results updated interface + register.RegisterResultsUpdatedEvent(pair); + + // Register plugin's action keywords so that plugins can be queried in results // set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin // has multiple global and action keywords because we will only add them here once. - foreach (var actionKeyword in plugin.Metadata.ActionKeywords.Distinct()) + foreach (var actionKeyword in pair.Metadata.ActionKeywords.Distinct()) { switch (actionKeyword) { case Query.GlobalPluginWildcardSign: - GlobalPlugins.Add(plugin); + _globalPlugins.TryAdd(pair.Metadata.ID, pair); break; default: - NonGlobalPlugins[actionKeyword] = plugin; + _nonGlobalPlugins.TryAdd(actionKeyword, pair); break; } } - } - if (failedPlugins.Any()) + // Update plugin metadata translation after the plugin is initialized with IPublicAPI instance + Internationalization.UpdatePluginMetadataTranslation(pair); + + // Add plugin to Dialog Jump plugin list after the plugin is initialized + DialogJump.InitializeDialogJumpPlugin(pair); + })); + + await Task.WhenAll(InitTasks); + + if (!failedPlugins.IsEmpty) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); API.ShowMsg( @@ -315,12 +336,12 @@ namespace Flow.Launcher.Core.Plugin if (query is null) return Array.Empty(); - if (!NonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) + if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) { if (dialogJump) - return GlobalPlugins.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID)).ToList(); + return _globalPlugins.Values.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID)).ToList(); else - return GlobalPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return _globalPlugins.Values.Where(p => !PluginModified(p.Metadata.ID)).ToList(); } if (dialogJump && plugin.Plugin is not IAsyncDialogJump) @@ -329,10 +350,10 @@ namespace Flow.Launcher.Core.Plugin if (API.PluginModified(plugin.Metadata.ID)) return Array.Empty(); - return new List - { + return + [ plugin - }; + ]; } public static ICollection ValidPluginsForHomeQuery() @@ -466,18 +487,7 @@ namespace Flow.Launcher.Core.Plugin /// public static PluginPair GetPluginForId(string id) { - return AllPlugins.FirstOrDefault(o => o.Metadata.ID == id); - } - - private static IEnumerable GetPluginsForInterface() where T : IFeatures - { - // Handle scenario where this is called before all plugins are instantiated, e.g. language change on startup - return AllPlugins?.Where(p => p.Plugin is T) ?? Array.Empty(); - } - - public static IList GetResultUpdatePlugin() - { - return _resultUpdatePlugin.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return GetAllPlugins().FirstOrDefault(o => o.Metadata.ID == id); } public static IList GetTranslationPlugins() @@ -519,14 +529,9 @@ namespace Flow.Launcher.Core.Plugin return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id); } - public static IList GetDialogJumpExplorers() + private static List GetExternalPreviewPlugins() { - return _dialogJumpExplorerPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); - } - - public static IList GetDialogJumpDialogs() - { - return _dialogJumpDialogPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return _externalPreviewPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); } public static bool ActionKeywordRegistered(string actionKeyword) @@ -534,7 +539,7 @@ namespace Flow.Launcher.Core.Plugin // this method is only checking for action keywords (defined as not '*') registration // hence the actionKeyword != Query.GlobalPluginWildcardSign logic return actionKeyword != Query.GlobalPluginWildcardSign - && NonGlobalPlugins.ContainsKey(actionKeyword); + && _nonGlobalPlugins.ContainsKey(actionKeyword); } /// @@ -546,11 +551,18 @@ namespace Flow.Launcher.Core.Plugin var plugin = GetPluginForId(id); if (newActionKeyword == Query.GlobalPluginWildcardSign) { - GlobalPlugins.Add(plugin); + _globalPlugins.TryAdd(id, plugin); } else { - NonGlobalPlugins[newActionKeyword] = plugin; + if (_nonGlobalPlugins.TryGetValue(newActionKeyword, out var item)) + { + _nonGlobalPlugins.TryUpdate(newActionKeyword, plugin, item); + } + else + { + _nonGlobalPlugins.TryAdd(newActionKeyword, plugin); + } } // Update action keywords and action keyword in plugin metadata @@ -577,11 +589,19 @@ namespace Flow.Launcher.Core.Plugin plugin.Metadata.ActionKeywords .Count(x => x == Query.GlobalPluginWildcardSign) == 1) { - GlobalPlugins.Remove(plugin); + _globalPlugins.TryRemove(id, out _); } if (oldActionkeyword != Query.GlobalPluginWildcardSign) - NonGlobalPlugins.Remove(oldActionkeyword); + { + _nonGlobalPlugins.TryRemove(oldActionkeyword, out var item); + // If the removed item is not the same as the plugin being removed, + // we should add it back to non-global plugins + if (item.Metadata.ID != id) + { + _nonGlobalPlugins.TryAdd(oldActionkeyword, item); + } + } // Update action keywords and action keyword in plugin metadata plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); @@ -620,7 +640,7 @@ namespace Flow.Launcher.Core.Plugin if (!Version.TryParse(newMetadata.Version, out var newVersion)) return true; // If version is not valid, we assume it is lesser than any existing version - return AllPlugins.Any(x => x.Metadata.ID == newMetadata.ID + return GetAllPlugins().Any(x => x.Metadata.ID == newMetadata.ID && Version.TryParse(x.Metadata.Version, out var version) && newVersion <= version); } @@ -760,7 +780,7 @@ namespace Flow.Launcher.Core.Plugin // If we want to remove plugin from AllPlugins, // we need to dispose them so that they can release file handles // which can help FL to delete the plugin settings & cache folders successfully - var pluginPairs = AllPlugins.FindAll(p => p.Metadata.ID == plugin.ID); + var pluginPairs = GetAllPlugins().Where(p => p.Metadata.ID == plugin.ID).ToList(); foreach (var pluginPair in pluginPairs) { await DisposePluginAsync(pluginPair); @@ -805,12 +825,12 @@ namespace Flow.Launcher.Core.Plugin string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); - AllPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); - GlobalPlugins.RemoveWhere(p => p.Metadata.ID == plugin.ID); - var keysToRemove = NonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); + _allPlugins.TryRemove(plugin.ID, out var item); + _globalPlugins.TryRemove(plugin.ID, out var item1); + var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); foreach (var key in keysToRemove) { - NonGlobalPlugins.Remove(key); + _nonGlobalPlugins.Remove(key, out var item2); } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 33d6c724c..82f49958c 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -209,23 +209,11 @@ namespace Flow.Launcher var imageLoadertask = ImageLoader.InitializeAsync(); - AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); - - PluginManager.LoadPlugins(_settings.PluginSettings); - - // Register ResultsUpdated event after all plugins are loaded - Ioc.Default.GetRequiredService().RegisterResultsUpdatedEvent(); - Http.Proxy = _settings.Proxy; // Initialize plugin manifest before initializing plugins so that they can use the manifest instantly await API.UpdatePluginManifestAsync(); - await PluginManager.InitializePluginsAsync(); - - // Update plugin titles after plugins are initialized with their api instances - Internationalization.UpdatePluginMetadataTranslations(); - await imageLoadertask; _mainWindow = new MainWindow(); @@ -235,7 +223,7 @@ namespace Flow.Launcher // Initialize quick jump before hotkey mapper since hotkey mapper will register quick jump hotkey // Initialize quick jump after main window is created so that it can access main window handle - DialogJump.InitializeDialogJump(PluginManager.GetDialogJumpExplorers(), PluginManager.GetDialogJumpDialogs()); + DialogJump.InitializeDialogJump(); DialogJump.SetupDialogJump(_settings.EnableDialogJump); // Initialize hotkey mapper instantly after main window is created because @@ -251,10 +239,27 @@ namespace Flow.Launcher AutoStartup(); AutoUpdates(); - AutoPluginUpdates(); API.SaveAppAllSettings(); - API.LogInfo(ClassName, "End Flow Launcher startup ----------------------------------------------------"); + API.LogInfo(ClassName, "End Flow Launcher startup ------------------------------------------------------"); + + _ = API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () => + { + API.LogInfo(ClassName, "Begin plugin initialization ----------------------------------------------------"); + + AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); + + var allPlugins = PluginManager.LoadPlugins(_settings.PluginSettings); + + await PluginManager.InitializePluginsAsync(allPlugins, _mainVM); + + AutoPluginUpdates(); + + // Save all settings since we possibly update the plugin environment paths + API.SaveAppAllSettings(); + + API.LogInfo(ClassName, "End plugin initialization ------------------------------------------------------"); + }); }); } diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 2ddce8190..2c7233218 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using System.Media; @@ -476,7 +476,7 @@ namespace Flow.Launcher && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) { var queryWithoutActionKeyword = - QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; + QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.GetNonGlobalPlugins())?.Search; if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) { diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index d865a087b..dc64aefa9 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -251,7 +251,7 @@ namespace Flow.Launcher public string GetTranslation(string key) => Internationalization.GetTranslation(key); - public List GetAllPlugins() => PluginManager.AllPlugins.ToList(); + public List GetAllPlugins() => PluginManager.GetAllPlugins(); public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index 3e1294bc2..9e20d7a4a 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -115,7 +115,7 @@ public partial class SettingsPanePluginsViewModel : BaseModel } private IList? _pluginViewModels; - public IList PluginViewModels => _pluginViewModels ??= PluginManager.AllPlugins + public IList PluginViewModels => _pluginViewModels ??= PluginManager.GetAllPlugins() .OrderBy(plugin => plugin.Metadata.Disabled) .ThenBy(plugin => plugin.Metadata.Name) .Select(plugin => new PluginViewModel diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 5816f1a85..0b491175f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -437,7 +437,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void Backspace(object index) { - var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.GetNonGlobalPlugins()); // GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\')); @@ -1573,7 +1573,7 @@ namespace Flow.Launcher.ViewModel { if (string.IsNullOrWhiteSpace(queryText)) { - return QueryBuilder.Build(string.Empty, PluginManager.NonGlobalPlugins); + return QueryBuilder.Build(string.Empty, PluginManager.GetNonGlobalPlugins()); } var queryBuilder = new StringBuilder(queryText); @@ -1593,7 +1593,7 @@ namespace Flow.Launcher.ViewModel // Applying builtin shortcuts await BuildQueryAsync(builtInShortcuts, queryBuilder, queryBuilderTmp); - return QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins); + return QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.GetNonGlobalPlugins()); } private async Task BuildQueryAsync(IEnumerable builtInShortcuts, From 59a7a2c807bbb161139296ad93551ebbb1224fd9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 15:59:27 +0800 Subject: [PATCH 007/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 51530705e..60e832f69 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -46,9 +46,9 @@ namespace Flow.Launcher.Core.Plugin /// Directories that will hold Flow Launcher plugin directory /// public static readonly string[] Directories = - { + [ Constant.PreinstalledDirectory, DataLocation.PluginsDirectory - }; + ]; private static void DeletePythonBinding() { @@ -121,39 +121,39 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadDataAsync() { - await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch { IReloadable p => Task.Run(p.ReloadData), IAsyncReloadable p => p.ReloadDataAsync(), _ => Task.CompletedTask, - }).ToArray()); + })]); } public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, - }).ToArray()); + })]); } public static async Task CloseExternalPreviewAsync() { - await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.ClosePreviewAsync(), _ => Task.CompletedTask, - }).ToArray()); + })]); } public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll(GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, - }).ToArray()); + })]); } public static bool UseExternalPreview() @@ -339,9 +339,9 @@ namespace Flow.Launcher.Core.Plugin if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) { if (dialogJump) - return _globalPlugins.Values.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID)).ToList(); + return [.. _globalPlugins.Values.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))]; else - return _globalPlugins.Values.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return [.. _globalPlugins.Values.Where(p => !PluginModified(p.Metadata.ID))]; } if (dialogJump && plugin.Plugin is not IAsyncDialogJump) @@ -350,15 +350,12 @@ namespace Flow.Launcher.Core.Plugin if (API.PluginModified(plugin.Metadata.ID)) return Array.Empty(); - return - [ - plugin - ]; + return [plugin]; } public static ICollection ValidPluginsForHomeQuery() { - return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return [.. _homePlugins.Where(p => !PluginModified(p.Metadata.ID))]; } public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) @@ -492,7 +489,7 @@ namespace Flow.Launcher.Core.Plugin public static IList GetTranslationPlugins() { - return _translationPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; } public static List GetContextMenusForPlugin(Result result) @@ -531,7 +528,7 @@ namespace Flow.Launcher.Core.Plugin private static List GetExternalPreviewPlugins() { - return _externalPreviewPlugins.Where(p => !PluginModified(p.Metadata.ID)).ToList(); + return [.. _externalPreviewPlugins.Where(p => !PluginModified(p.Metadata.ID))]; } public static bool ActionKeywordRegistered(string actionKeyword) From 52bb909f0bdd404376d510b498854688e0df94c8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:00:42 +0800 Subject: [PATCH 008/125] Add plugin to all plugin list later --- Flow.Launcher.Core/Plugin/PluginManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 60e832f69..8e03171ca 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -244,9 +244,6 @@ namespace Flow.Launcher.Core.Plugin pair.Metadata.InitTime += milliseconds; API.LogInfo(ClassName, $"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); - - // Add it in all plugin list - _allPlugins.TryAdd(pair.Metadata.ID, pair); } catch (Exception e) { @@ -312,6 +309,9 @@ namespace Flow.Launcher.Core.Plugin // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); + + // Add plugin to all plugin list + _allPlugins.TryAdd(pair.Metadata.ID, pair); })); await Task.WhenAll(InitTasks); From 0a01d85f4051f298d83b22fc024c6f0474e464b6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:16:21 +0800 Subject: [PATCH 009/125] Improve code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0b491175f..2299e4e98 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1327,7 +1327,7 @@ namespace Flow.Launcher.ViewModel private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - _updateSource?.Cancel(); + await _updateSource?.CancelAsync(); App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1906,7 +1906,7 @@ namespace Flow.Launcher.ViewModel if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - _dialogJumpSource?.Cancel(); + await _dialogJumpSource?.CancelAsync(); // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource(); From c3a598464f695e9e5b3ad8fda6827aeb555bb03e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:20:09 +0800 Subject: [PATCH 010/125] Fix code comments --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 8e03171ca..bb6b8022e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -180,10 +180,10 @@ namespace Flow.Launcher.Core.Plugin } /// - /// because InitializePlugins needs API, so LoadPlugins needs to be called first - /// todo happlebao The API should be removed + /// Load plugins from the directories specified in Directories. /// /// + /// public static List LoadPlugins(PluginsSettings settings) { var metadatas = PluginConfig.Parse(Directories); From 35c8e39beb7ea054309c323ad5286a089222e32d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:23:40 +0800 Subject: [PATCH 011/125] Improve code comments --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index bb6b8022e..89b4e65e4 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -227,8 +227,10 @@ namespace Flow.Launcher.Core.Plugin } /// - /// Call initialize for all plugins + /// Initialize all plugins asynchronously. /// + /// List of all plugins to initialize. + /// The register to register results updated event for each plugin. /// return the list of failed to init plugins or null for none public static async Task InitializePluginsAsync(List allPlugins, IResultUpdateRegister register) { From 1d3ab39dca4919b96052d943b4ac3eccee0cdf54 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:24:23 +0800 Subject: [PATCH 012/125] Use () => instead of delegate --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 89b4e65e4..fe73a763b 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -236,7 +236,7 @@ namespace Flow.Launcher.Core.Plugin { var failedPlugins = new ConcurrentQueue(); - var InitTasks = allPlugins.Select(pair => Task.Run(async delegate + var InitTasks = allPlugins.Select(pair => Task.Run(async () => { try { From 445a14278b2f95d684cbc9c89044d6e9b831584f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:25:26 +0800 Subject: [PATCH 013/125] Add code comments --- Flow.Launcher.Core/Plugin/PluginManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index fe73a763b..96dce620e 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -264,7 +264,8 @@ namespace Flow.Launcher.Core.Plugin API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } - // Even if the plugin cannot be initialized, we still need to add it in all plugin list + // Even if the plugin cannot be initialized, we still need to add it in all plugin list so that + // we can remove the plugin from Plugin or Store page or Plugin Manager plugin. _allPlugins.TryAdd(pair.Metadata.ID, pair); return; } From de568140d238ab2a74f72f783ebca6e3738b56e2 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:27:11 +0800 Subject: [PATCH 014/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 43 ++++++++++++---------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 96dce620e..5edf43a33 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -270,24 +270,6 @@ namespace Flow.Launcher.Core.Plugin return; } - // Initialize plugin lists after the plugin is initialized - if (pair.Plugin is IContextMenu) - { - _contextMenuPlugins.Add(pair); - } - if (pair.Plugin is IAsyncHomeQuery) - { - _homePlugins.Add(pair); - } - if (pair.Plugin is IPluginI18n) - { - _translationPlugins.Add(pair); - } - if (pair.Plugin is IAsyncExternalPreview) - { - _externalPreviewPlugins.Add(pair); - } - // Register ResultsUpdated event so that plugin query can use results updated interface register.RegisterResultsUpdatedEvent(pair); @@ -313,8 +295,8 @@ namespace Flow.Launcher.Core.Plugin // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); - // Add plugin to all plugin list - _allPlugins.TryAdd(pair.Metadata.ID, pair); + // Add plugin to lists after the plugin is initialized + AddPluginToLists(pair); })); await Task.WhenAll(InitTasks); @@ -334,6 +316,27 @@ namespace Flow.Launcher.Core.Plugin } } + private static void AddPluginToLists(PluginPair pair) + { + if (pair.Plugin is IContextMenu) + { + _contextMenuPlugins.Add(pair); + } + if (pair.Plugin is IAsyncHomeQuery) + { + _homePlugins.Add(pair); + } + if (pair.Plugin is IPluginI18n) + { + _translationPlugins.Add(pair); + } + if (pair.Plugin is IAsyncExternalPreview) + { + _externalPreviewPlugins.Add(pair); + } + _allPlugins.TryAdd(pair.Metadata.ID, pair); + } + public static ICollection ValidPluginsForQuery(Query query, bool dialogJump) { if (query is null) From 55164ef60ffd9b10bcae9b2c500c4a139f692b83 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:29:01 +0800 Subject: [PATCH 015/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 37 ++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 5edf43a33..e985be518 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -273,25 +273,12 @@ namespace Flow.Launcher.Core.Plugin // Register ResultsUpdated event so that plugin query can use results updated interface register.RegisterResultsUpdatedEvent(pair); - // Register plugin's action keywords so that plugins can be queried in results - // set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin - // has multiple global and action keywords because we will only add them here once. - foreach (var actionKeyword in pair.Metadata.ActionKeywords.Distinct()) - { - switch (actionKeyword) - { - case Query.GlobalPluginWildcardSign: - _globalPlugins.TryAdd(pair.Metadata.ID, pair); - break; - default: - _nonGlobalPlugins.TryAdd(actionKeyword, pair); - break; - } - } - // Update plugin metadata translation after the plugin is initialized with IPublicAPI instance Internationalization.UpdatePluginMetadataTranslation(pair); + // Register plugin action keywords so that plugins can be queried in results + RegisterPluginActionKeywords(pair); + // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); @@ -316,6 +303,24 @@ namespace Flow.Launcher.Core.Plugin } } + private static void RegisterPluginActionKeywords(PluginPair pair) + { + // set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin + // has multiple global and action keywords because we will only add them here once. + foreach (var actionKeyword in pair.Metadata.ActionKeywords.Distinct()) + { + switch (actionKeyword) + { + case Query.GlobalPluginWildcardSign: + _globalPlugins.TryAdd(pair.Metadata.ID, pair); + break; + default: + _nonGlobalPlugins.TryAdd(actionKeyword, pair); + break; + } + } + } + private static void AddPluginToLists(PluginPair pair) { if (pair.Plugin is IContextMenu) From cc681839402d5f5a7f5a16044adddbed98cbe35d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:30:39 +0800 Subject: [PATCH 016/125] Change variable name --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index e985be518..aece1e008 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -236,7 +236,7 @@ namespace Flow.Launcher.Core.Plugin { var failedPlugins = new ConcurrentQueue(); - var InitTasks = allPlugins.Select(pair => Task.Run(async () => + var initTasks = allPlugins.Select(pair => Task.Run(async () => { try { @@ -286,7 +286,7 @@ namespace Flow.Launcher.Core.Plugin AddPluginToLists(pair); })); - await Task.WhenAll(InitTasks); + await Task.WhenAll(initTasks); if (!failedPlugins.IsEmpty) { From 50924e418921df3c6ea23dbe19bcdd17d36523ba Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:45:34 +0800 Subject: [PATCH 017/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 109 +++++++++++++++------ 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index aece1e008..745b96ff3 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -50,24 +50,7 @@ namespace Flow.Launcher.Core.Plugin Constant.PreinstalledDirectory, DataLocation.PluginsDirectory ]; - private static void DeletePythonBinding() - { - const string binding = "flowlauncher.py"; - foreach (var subDirectory in Directory.GetDirectories(DataLocation.PluginsDirectory)) - { - File.Delete(Path.Combine(subDirectory, binding)); - } - } - - public static List GetAllPlugins() - { - return [.. _allPlugins.Values]; - } - - public static Dictionary GetNonGlobalPlugins() - { - return _nonGlobalPlugins.ToDictionary(); - } + #region Save & Dispose & Reload Plugin /// /// Save json and ISavable @@ -129,6 +112,10 @@ namespace Flow.Launcher.Core.Plugin })]); } + #endregion + + #region External Preview + public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch @@ -171,6 +158,15 @@ namespace Flow.Launcher.Core.Plugin return ((IAsyncExternalPreview)plugin.Plugin).AllowAlwaysPreview(); } + private static IList GetExternalPreviewPlugins() + { + return [.. _externalPreviewPlugins.Where(p => !PluginModified(p.Metadata.ID))]; + } + + #endregion + + #region Constructor + static PluginManager() { // validate user directory @@ -179,6 +175,19 @@ namespace Flow.Launcher.Core.Plugin DeletePythonBinding(); } + private static void DeletePythonBinding() + { + const string binding = "flowlauncher.py"; + foreach (var subDirectory in Directory.GetDirectories(DataLocation.PluginsDirectory)) + { + File.Delete(Path.Combine(subDirectory, binding)); + } + } + + #endregion + + #region Load & Initialize Plugins + /// /// Load plugins from the directories specified in Directories. /// @@ -342,6 +351,10 @@ namespace Flow.Launcher.Core.Plugin _allPlugins.TryAdd(pair.Metadata.ID, pair); } + #endregion + + #region Validate & Query Plugins + public static ICollection ValidPluginsForQuery(Query query, bool dialogJump) { if (query is null) @@ -350,9 +363,9 @@ namespace Flow.Launcher.Core.Plugin if (!_nonGlobalPlugins.TryGetValue(query.ActionKeyword, out var plugin)) { if (dialogJump) - return [.. _globalPlugins.Values.Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))]; + return [.. GetGlobalPlugins().Where(p => p.Plugin is IAsyncDialogJump && !PluginModified(p.Metadata.ID))]; else - return [.. _globalPlugins.Values.Where(p => !PluginModified(p.Metadata.ID))]; + return [.. GetGlobalPlugins().Where(p => !PluginModified(p.Metadata.ID))]; } if (dialogJump && plugin.Plugin is not IAsyncDialogJump) @@ -473,6 +486,10 @@ namespace Flow.Launcher.Core.Plugin return results; } + #endregion + + #region Update Metadata & Get Plugin + public static void UpdatePluginMetadata(IReadOnlyList results, PluginMetadata metadata, Query query) { foreach (var r in results) @@ -498,12 +515,35 @@ namespace Flow.Launcher.Core.Plugin return GetAllPlugins().FirstOrDefault(o => o.Metadata.ID == id); } - public static IList GetTranslationPlugins() + #endregion + + #region Get Plugin List + + public static List GetAllPlugins() + { + return [.. _allPlugins.Values]; + } + + public static List GetGlobalPlugins() + { + return [.. _globalPlugins.Values]; + } + + public static Dictionary GetNonGlobalPlugins() + { + return _nonGlobalPlugins.ToDictionary(); + } + + public static List GetTranslationPlugins() { return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; } - public static List GetContextMenusForPlugin(Result result) + #endregion + + #region Get Context Menus + + public static IList GetContextMenusForPlugin(Result result) { var results = new List(); var pluginPair = _contextMenuPlugins.Where(p => !PluginModified(p.Metadata.ID)).FirstOrDefault(o => o.Metadata.ID == result.PluginID); @@ -532,15 +572,18 @@ namespace Flow.Launcher.Core.Plugin return results; } + #endregion + + #region Check Home Plugin + public static bool IsHomePlugin(string id) { return _homePlugins.Where(p => !PluginModified(p.Metadata.ID)).Any(p => p.Metadata.ID == id); } - private static List GetExternalPreviewPlugins() - { - return [.. _externalPreviewPlugins.Where(p => !PluginModified(p.Metadata.ID))]; - } + #endregion + + #region Plugin Action Keyword public static bool ActionKeywordRegistered(string actionKeyword) { @@ -623,6 +666,12 @@ namespace Flow.Launcher.Core.Plugin } } + #endregion + + #region Plugin Install & Uninstall & Update + + #region Private Functions + private static string GetContainingFolderPathAfterUnzip(string unzippedParentFolderPath) { var unzippedFolderCount = Directory.GetDirectories(unzippedParentFolderPath).Length; @@ -653,7 +702,9 @@ namespace Flow.Launcher.Core.Plugin && newVersion <= version); } - #region Public functions + #endregion + + #region Public Functions public static bool PluginModified(string id) { @@ -691,7 +742,7 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Internal functions + #region Internal Functions internal static bool InstallPlugin(UserPlugin plugin, string zipFilePath, bool checkModified) { @@ -854,5 +905,7 @@ namespace Flow.Launcher.Core.Plugin } #endregion + + #endregion } } From 8b60d26f5e72b2268f9377cb726d3d24b521780e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:56:14 +0800 Subject: [PATCH 018/125] Fix build issue --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 745b96ff3..81528b8d6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -543,7 +543,7 @@ namespace Flow.Launcher.Core.Plugin #region Get Context Menus - public static IList GetContextMenusForPlugin(Result result) + public static List GetContextMenusForPlugin(Result result) { var results = new List(); var pluginPair = _contextMenuPlugins.Where(p => !PluginModified(p.Metadata.ID)).FirstOrDefault(o => o.Metadata.ID == result.PluginID); From 566572b013da3d51d2005cf48dd7134d5e75a284 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:57:35 +0800 Subject: [PATCH 019/125] Use api function & rename function --- Flow.Launcher.Core/Plugin/PluginManager.cs | 20 +++++++++---------- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- Flow.Launcher/PublicAPIInstance.cs | 2 +- .../SettingsPanePluginsViewModel.cs | 5 +++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 81528b8d6..fdd4c7fed 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -57,7 +57,7 @@ namespace Flow.Launcher.Core.Plugin /// public static void Save() { - foreach (var pluginPair in GetAllPlugins()) + foreach (var pluginPair in GetAllInitializedPlugins()) { var savable = pluginPair.Plugin as ISavable; try @@ -76,7 +76,7 @@ namespace Flow.Launcher.Core.Plugin public static async ValueTask DisposePluginsAsync() { - foreach (var pluginPair in GetAllPlugins()) + foreach (var pluginPair in GetAllInitializedPlugins()) { await DisposePluginAsync(pluginPair); } @@ -104,7 +104,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadDataAsync() { - await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch { IReloadable p => Task.Run(p.ReloadData), IAsyncReloadable p => p.ReloadDataAsync(), @@ -118,7 +118,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -127,7 +127,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task CloseExternalPreviewAsync() { - await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.ClosePreviewAsync(), _ => Task.CompletedTask, @@ -136,7 +136,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -512,14 +512,14 @@ namespace Flow.Launcher.Core.Plugin /// public static PluginPair GetPluginForId(string id) { - return GetAllPlugins().FirstOrDefault(o => o.Metadata.ID == id); + return GetAllInitializedPlugins().FirstOrDefault(o => o.Metadata.ID == id); } #endregion #region Get Plugin List - public static List GetAllPlugins() + public static List GetAllInitializedPlugins() { return [.. _allPlugins.Values]; } @@ -697,7 +697,7 @@ namespace Flow.Launcher.Core.Plugin if (!Version.TryParse(newMetadata.Version, out var newVersion)) return true; // If version is not valid, we assume it is lesser than any existing version - return GetAllPlugins().Any(x => x.Metadata.ID == newMetadata.ID + return GetAllInitializedPlugins().Any(x => x.Metadata.ID == newMetadata.ID && Version.TryParse(x.Metadata.Version, out var version) && newVersion <= version); } @@ -839,7 +839,7 @@ namespace Flow.Launcher.Core.Plugin // If we want to remove plugin from AllPlugins, // we need to dispose them so that they can release file handles // which can help FL to delete the plugin settings & cache folders successfully - var pluginPairs = GetAllPlugins().Where(p => p.Metadata.ID == plugin.ID).ToList(); + var pluginPairs = GetAllInitializedPlugins().Where(p => p.Metadata.ID == plugin.ID).ToList(); foreach (var pluginPair in pluginPairs) { await DisposePluginAsync(pluginPair); diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index cfa813d3f..4cefe4bc6 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -171,7 +171,7 @@ namespace Flow.Launcher.Plugin string GetTranslation(string key); /// - /// Get all loaded plugins + /// Get all initialized plugins /// /// List GetAllPlugins(); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index dc64aefa9..bae953bbc 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -251,7 +251,7 @@ namespace Flow.Launcher public string GetTranslation(string key) => Internationalization.GetTranslation(key); - public List GetAllPlugins() => PluginManager.GetAllPlugins(); + public List GetAllPlugins() => PluginManager.GetAllInitializedPlugins(); public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index 9e20d7a4a..8a84a7749 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -114,8 +114,9 @@ public partial class SettingsPanePluginsViewModel : BaseModel } } - private IList? _pluginViewModels; - public IList PluginViewModels => _pluginViewModels ??= PluginManager.GetAllPlugins() + private List? _pluginViewModels; + // Get all initialized plugins and ignore those that are not initialized + public List PluginViewModels => _pluginViewModels ??= App.API.GetAllPlugins() .OrderBy(plugin => plugin.Metadata.Disabled) .ThenBy(plugin => plugin.Metadata.Name) .Select(plugin => new PluginViewModel From 11e05f73d99a9df4ecb09daad49a4f836530a9d3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:00:19 +0800 Subject: [PATCH 020/125] Add all loaded plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 28 ++++++++++++---------- Flow.Launcher/App.xaml.cs | 4 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index fdd4c7fed..bce2b1fe6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -30,7 +30,8 @@ namespace Flow.Launcher.Core.Plugin private static IPublicAPI api = null; private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); - private static readonly ConcurrentDictionary _allPlugins = []; + private static List _allLoadedPlugins; + private static readonly ConcurrentDictionary _allInitializedPlugins = []; private static readonly ConcurrentDictionary _globalPlugins = []; private static readonly ConcurrentDictionary _nonGlobalPlugins = []; @@ -192,20 +193,17 @@ namespace Flow.Launcher.Core.Plugin /// Load plugins from the directories specified in Directories. /// /// - /// - public static List LoadPlugins(PluginsSettings settings) + public static void LoadPlugins(PluginsSettings settings) { var metadatas = PluginConfig.Parse(Directories); Settings = settings; Settings.UpdatePluginSettings(metadatas); // Load plugins - var allPlugins = PluginsLoader.Plugins(metadatas, Settings); + _allLoadedPlugins = PluginsLoader.Plugins(metadatas, Settings); // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins UpdatePluginDirectory(metadatas); - - return allPlugins; } private static void UpdatePluginDirectory(List metadatas) @@ -238,14 +236,13 @@ namespace Flow.Launcher.Core.Plugin /// /// Initialize all plugins asynchronously. /// - /// List of all plugins to initialize. /// The register to register results updated event for each plugin. /// return the list of failed to init plugins or null for none - public static async Task InitializePluginsAsync(List allPlugins, IResultUpdateRegister register) + public static async Task InitializePluginsAsync(IResultUpdateRegister register) { var failedPlugins = new ConcurrentQueue(); - var initTasks = allPlugins.Select(pair => Task.Run(async () => + var initTasks = _allLoadedPlugins.Select(pair => Task.Run(async () => { try { @@ -275,7 +272,7 @@ namespace Flow.Launcher.Core.Plugin // Even if the plugin cannot be initialized, we still need to add it in all plugin list so that // we can remove the plugin from Plugin or Store page or Plugin Manager plugin. - _allPlugins.TryAdd(pair.Metadata.ID, pair); + _allInitializedPlugins.TryAdd(pair.Metadata.ID, pair); return; } @@ -348,7 +345,7 @@ namespace Flow.Launcher.Core.Plugin { _externalPreviewPlugins.Add(pair); } - _allPlugins.TryAdd(pair.Metadata.ID, pair); + _allInitializedPlugins.TryAdd(pair.Metadata.ID, pair); } #endregion @@ -519,9 +516,14 @@ namespace Flow.Launcher.Core.Plugin #region Get Plugin List + public static List GetAllLoadedPlugins() + { + return [.. _allLoadedPlugins]; + } + public static List GetAllInitializedPlugins() { - return [.. _allPlugins.Values]; + return [.. _allInitializedPlugins.Values]; } public static List GetGlobalPlugins() @@ -884,7 +886,7 @@ namespace Flow.Launcher.Core.Plugin string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); - _allPlugins.TryRemove(plugin.ID, out var item); + _allInitializedPlugins.TryRemove(plugin.ID, out var item); _globalPlugins.TryRemove(plugin.ID, out var item1); var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); foreach (var key in keysToRemove) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 82f49958c..907833700 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -249,9 +249,9 @@ namespace Flow.Launcher AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); - var allPlugins = PluginManager.LoadPlugins(_settings.PluginSettings); + PluginManager.LoadPlugins(_settings.PluginSettings); - await PluginManager.InitializePluginsAsync(allPlugins, _mainVM); + await PluginManager.InitializePluginsAsync(_mainVM); AutoPluginUpdates(); From fe3339f645c49ea5b3a7c19b9f9e70c4d80ced95 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:02:19 +0800 Subject: [PATCH 021/125] Get initialized plugins for plugin page --- .../SettingPages/ViewModels/SettingsPanePluginsViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index 8a84a7749..5254cdfde 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -116,7 +116,7 @@ public partial class SettingsPanePluginsViewModel : BaseModel private List? _pluginViewModels; // Get all initialized plugins and ignore those that are not initialized - public List PluginViewModels => _pluginViewModels ??= App.API.GetAllPlugins() + public List PluginViewModels => _pluginViewModels ??= PluginManager.GetAllInitializedPlugins() .OrderBy(plugin => plugin.Metadata.Disabled) .ThenBy(plugin => plugin.Metadata.Name) .Select(plugin => new PluginViewModel From 6e0f2fc4ced1830e593e6c44fa3251881dfe4931 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:03:21 +0800 Subject: [PATCH 022/125] Return loaded plugins --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 5 ++++- Flow.Launcher/PublicAPIInstance.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 4cefe4bc6..269635d5e 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -171,8 +171,11 @@ namespace Flow.Launcher.Plugin string GetTranslation(string key); /// - /// Get all initialized plugins + /// Get all loaded plugins /// + /// + /// Part of plugins may not be initialized yet + /// /// List GetAllPlugins(); diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index bae953bbc..69ca0c01d 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -251,7 +251,7 @@ namespace Flow.Launcher public string GetTranslation(string key) => Internationalization.GetTranslation(key); - public List GetAllPlugins() => PluginManager.GetAllInitializedPlugins(); + public List GetAllPlugins() => PluginManager.GetAllLoadedPlugins(); public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); From 6d994166417e23402246cf32cc5a44449f9ec3bd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:14:03 +0800 Subject: [PATCH 023/125] Search in loaded plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index bce2b1fe6..d6d4c5eae 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -505,11 +505,14 @@ namespace Flow.Launcher.Core.Plugin /// /// get specified plugin, return null if not found /// + /// + /// Plugin may not be initialized, so do not use its plugin model to execute any commands + /// /// /// public static PluginPair GetPluginForId(string id) { - return GetAllInitializedPlugins().FirstOrDefault(o => o.Metadata.ID == id); + return GetAllLoadedPlugins().FirstOrDefault(o => o.Metadata.ID == id); } #endregion From 9149e3f201c9155c22405c47760692af384a8c51 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:16:36 +0800 Subject: [PATCH 024/125] Mark initializing plugins as modified --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d6d4c5eae..5ea978418 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -713,7 +713,7 @@ namespace Flow.Launcher.Core.Plugin public static bool PluginModified(string id) { - return ModifiedPlugins.Contains(id); + return ModifiedPlugins.Contains(id) && _allInitializedPlugins.ContainsKey(id); } public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 269635d5e..456341a71 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -534,7 +534,8 @@ namespace Flow.Launcher.Plugin /// /// Check if the plugin has been modified. - /// If this plugin is updated, installed or uninstalled and users do not restart the app, + /// If this plugin is initializing, it will be marked as modified. + /// Or if this plugin is updated, installed or uninstalled and users do not restart the app, /// it will be marked as modified /// /// Plugin id From 8d03fcee2edeb6cb964e9acd0e71744aa0ae68a5 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:17:11 +0800 Subject: [PATCH 025/125] Remove error codes --- Flow.Launcher.Core/Plugin/PluginManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 5ea978418..635690ce2 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -651,12 +651,6 @@ namespace Flow.Launcher.Core.Plugin if (oldActionkeyword != Query.GlobalPluginWildcardSign) { _nonGlobalPlugins.TryRemove(oldActionkeyword, out var item); - // If the removed item is not the same as the plugin being removed, - // we should add it back to non-global plugins - if (item.Metadata.ID != id) - { - _nonGlobalPlugins.TryAdd(oldActionkeyword, item); - } } // Update action keywords and action keyword in plugin metadata From 3bd76b2dfd73b0e95753fc3df617635eb4a51ae9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:17:49 +0800 Subject: [PATCH 026/125] Rename variable --- Flow.Launcher/App.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 907833700..7bef6ae97 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -207,14 +207,14 @@ namespace Flow.Launcher RegisterDispatcherUnhandledException(); RegisterTaskSchedulerUnhandledException(); - var imageLoadertask = ImageLoader.InitializeAsync(); + var imageLoaderTask = ImageLoader.InitializeAsync(); Http.Proxy = _settings.Proxy; // Initialize plugin manifest before initializing plugins so that they can use the manifest instantly await API.UpdatePluginManifestAsync(); - await imageLoadertask; + await imageLoaderTask; _mainWindow = new MainWindow(); From 7f797b1039ced8e71b24322ce11afe9b336fd58e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:22:17 +0800 Subject: [PATCH 027/125] Add code comments --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 635690ce2..4e2ed0762 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -707,6 +707,8 @@ namespace Flow.Launcher.Core.Plugin public static bool PluginModified(string id) { + // We should consider initializing plugin as modified since it cannot be installed/uninstalled/updated and + // we cannot call any plugin methods return ModifiedPlugins.Contains(id) && _allInitializedPlugins.ContainsKey(id); } From fc01ddbb1ceb8b0a4ef0213da8db731fc5e1d91c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:27:21 +0800 Subject: [PATCH 028/125] Save init failed plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 4e2ed0762..893e36434 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -31,6 +31,7 @@ namespace Flow.Launcher.Core.Plugin private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); private static List _allLoadedPlugins; + private static readonly ConcurrentDictionary _initFailedPlugins = []; private static readonly ConcurrentDictionary _allInitializedPlugins = []; private static readonly ConcurrentDictionary _globalPlugins = []; private static readonly ConcurrentDictionary _nonGlobalPlugins = []; @@ -240,8 +241,6 @@ namespace Flow.Launcher.Core.Plugin /// return the list of failed to init plugins or null for none public static async Task InitializePluginsAsync(IResultUpdateRegister register) { - var failedPlugins = new ConcurrentQueue(); - var initTasks = _allLoadedPlugins.Select(pair => Task.Run(async () => { try @@ -266,7 +265,7 @@ namespace Flow.Launcher.Core.Plugin { pair.Metadata.Disabled = true; pair.Metadata.HomeDisabled = true; - failedPlugins.Enqueue(pair); + _initFailedPlugins.TryAdd(pair.Metadata.ID, pair); API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } @@ -294,9 +293,9 @@ namespace Flow.Launcher.Core.Plugin await Task.WhenAll(initTasks); - if (!failedPlugins.IsEmpty) + if (!_initFailedPlugins.IsEmpty) { - var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); + var failed = string.Join(",", _initFailedPlugins.Values.Select(x => x.Metadata.Name)); API.ShowMsg( API.GetTranslation("failedToInitializePluginsTitle"), string.Format( From b348debc72690854ce88a6a29585e73a2dfd24bb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:27:37 +0800 Subject: [PATCH 029/125] Code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 893e36434..3dc07c5ec 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -31,8 +31,8 @@ namespace Flow.Launcher.Core.Plugin private static IPublicAPI API => api ??= Ioc.Default.GetRequiredService(); private static List _allLoadedPlugins; - private static readonly ConcurrentDictionary _initFailedPlugins = []; private static readonly ConcurrentDictionary _allInitializedPlugins = []; + private static readonly ConcurrentDictionary _initFailedPlugins = []; private static readonly ConcurrentDictionary _globalPlugins = []; private static readonly ConcurrentDictionary _nonGlobalPlugins = []; From d4a19537479233d0058cc9e4c7b4988a741c6656 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:28:20 +0800 Subject: [PATCH 030/125] Add init failed plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 3dc07c5ec..7f9a4a539 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -265,13 +265,13 @@ namespace Flow.Launcher.Core.Plugin { pair.Metadata.Disabled = true; pair.Metadata.HomeDisabled = true; - _initFailedPlugins.TryAdd(pair.Metadata.ID, pair); API.LogDebug(ClassName, $"Disable plugin <{pair.Metadata.Name}> because init failed"); } // Even if the plugin cannot be initialized, we still need to add it in all plugin list so that // we can remove the plugin from Plugin or Store page or Plugin Manager plugin. _allInitializedPlugins.TryAdd(pair.Metadata.ID, pair); + _initFailedPlugins.TryAdd(pair.Metadata.ID, pair); return; } From 324b3eb08171b19146c181ed4a2f781cd79757f1 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:33:47 +0800 Subject: [PATCH 031/125] Do not call interface methods for init failed plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 39 ++++++++++++------- .../SettingsPanePluginsViewModel.cs | 3 +- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7f9a4a539..f69a4ba0a 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -59,7 +59,7 @@ namespace Flow.Launcher.Core.Plugin /// public static void Save() { - foreach (var pluginPair in GetAllInitializedPlugins()) + foreach (var pluginPair in GetAllInitializedPlugins(false)) { var savable = pluginPair.Plugin as ISavable; try @@ -78,7 +78,8 @@ namespace Flow.Launcher.Core.Plugin public static async ValueTask DisposePluginsAsync() { - foreach (var pluginPair in GetAllInitializedPlugins()) + // Still call dispose for all plugins even if initialization failed, so that we can clean up resources + foreach (var pluginPair in GetAllInitializedPlugins(true)) { await DisposePluginAsync(pluginPair); } @@ -106,7 +107,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadDataAsync() { - await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch { IReloadable p => Task.Run(p.ReloadData), IAsyncReloadable p => p.ReloadDataAsync(), @@ -120,7 +121,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -129,7 +130,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task CloseExternalPreviewAsync() { - await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.ClosePreviewAsync(), _ => Task.CompletedTask, @@ -138,7 +139,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllInitializedPlugins().Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -523,9 +524,17 @@ namespace Flow.Launcher.Core.Plugin return [.. _allLoadedPlugins]; } - public static List GetAllInitializedPlugins() + public static List GetAllInitializedPlugins(bool containFailed) { - return [.. _allInitializedPlugins.Values]; + if (containFailed) + { + return [.. _allInitializedPlugins.Values]; + } + else + { + return [.. _allInitializedPlugins.Values + .Where(p => !_initFailedPlugins.ContainsKey(p.Metadata.ID))]; + } } public static List GetGlobalPlugins() @@ -695,9 +704,10 @@ namespace Flow.Launcher.Core.Plugin if (!Version.TryParse(newMetadata.Version, out var newVersion)) return true; // If version is not valid, we assume it is lesser than any existing version - return GetAllInitializedPlugins().Any(x => x.Metadata.ID == newMetadata.ID - && Version.TryParse(x.Metadata.Version, out var version) - && newVersion <= version); + // Get all plugins even if initialization failed so that we can check if the plugin with the same ID exists + return GetAllInitializedPlugins(true).Any(x => x.Metadata.ID == newMetadata.ID + && Version.TryParse(x.Metadata.Version, out var version) + && newVersion <= version); } #endregion @@ -839,7 +849,7 @@ namespace Flow.Launcher.Core.Plugin // If we want to remove plugin from AllPlugins, // we need to dispose them so that they can release file handles // which can help FL to delete the plugin settings & cache folders successfully - var pluginPairs = GetAllInitializedPlugins().Where(p => p.Metadata.ID == plugin.ID).ToList(); + var pluginPairs = GetAllInitializedPlugins(true).Where(p => p.Metadata.ID == plugin.ID).ToList(); foreach (var pluginPair in pluginPairs) { await DisposePluginAsync(pluginPair); @@ -885,11 +895,12 @@ namespace Flow.Launcher.Core.Plugin } Settings.RemovePluginSettings(plugin.ID); _allInitializedPlugins.TryRemove(plugin.ID, out var item); - _globalPlugins.TryRemove(plugin.ID, out var item1); + _initFailedPlugins.TryRemove(plugin.ID, out var item1); + _globalPlugins.TryRemove(plugin.ID, out var item2); var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); foreach (var key in keysToRemove) { - _nonGlobalPlugins.Remove(key, out var item2); + _nonGlobalPlugins.Remove(key, out var item3); } } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index 5254cdfde..b7723276e 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -116,7 +116,8 @@ public partial class SettingsPanePluginsViewModel : BaseModel private List? _pluginViewModels; // Get all initialized plugins and ignore those that are not initialized - public List PluginViewModels => _pluginViewModels ??= PluginManager.GetAllInitializedPlugins() + // Include those init failed plugins so that we can uninstall them + public List PluginViewModels => _pluginViewModels ??= PluginManager.GetAllInitializedPlugins(true) .OrderBy(plugin => plugin.Metadata.Disabled) .ThenBy(plugin => plugin.Metadata.Name) .Select(plugin => new PluginViewModel From 67c940f3a8f20351369d4516ab353a495ea8543f Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:45:01 +0800 Subject: [PATCH 032/125] Do not show setting panel for init failed plugins --- Flow.Launcher.Core/Plugin/PluginManager.cs | 11 ++++++++++- Flow.Launcher/ViewModel/PluginViewModel.cs | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index f69a4ba0a..3ba89fce6 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -533,7 +533,7 @@ namespace Flow.Launcher.Core.Plugin else { return [.. _allInitializedPlugins.Values - .Where(p => !_initFailedPlugins.ContainsKey(p.Metadata.ID))]; + .Where(p => !IsInitFailed(p.Metadata.ID))]; } } @@ -596,6 +596,15 @@ namespace Flow.Launcher.Core.Plugin #endregion + #region Check Init Failed + + public static bool IsInitFailed(string id) + { + return _initFailedPlugins.ContainsKey(id); + } + + #endregion + #region Plugin Action Keyword public static bool ActionKeywordRegistered(string actionKeyword) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index ea222d023..5fc3cd0cd 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -121,7 +121,9 @@ namespace Flow.Launcher.ViewModel public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public bool HasSettingControl => PluginPair.Plugin is ISettingProvider && + PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel()); + public Control SettingControl => IsExpanded ? _settingControl From 950a4a00a6d61e8436f12a427b4de493fbcb58b4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:45:46 +0800 Subject: [PATCH 033/125] Code quality --- Flow.Launcher/ViewModel/PluginViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 5fc3cd0cd..cd58b4104 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -120,8 +120,9 @@ namespace Flow.Launcher.ViewModel private Control _bottomPart2; public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; - public bool HasSettingControl => PluginPair.Plugin is ISettingProvider && + public bool HasSettingControl => PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins + PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel()); public Control SettingControl From 6409c193c4642d63ff3e4cc516d46cc4f0e7a0a9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:47:19 +0800 Subject: [PATCH 034/125] Add code comments --- Flow.Launcher/ViewModel/PluginViewModel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index cd58b4104..a8f4ef432 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -123,7 +123,8 @@ namespace Flow.Launcher.ViewModel public bool HasSettingControl => PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins PluginPair.Plugin is ISettingProvider && - (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || jsonRPCPluginBase.NeedCreateSettingPanel()); + (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || // Is not JsonRPC plugin + jsonRPCPluginBase.NeedCreateSettingPanel()); // Is JsonRPC plugin and need to create setting panel public Control SettingControl => IsExpanded From a9a705f118e7832f8d49cfb71b78aa82bc0e893a Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:48:17 +0800 Subject: [PATCH 035/125] Fix logic Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher/ViewModel/PluginViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index a8f4ef432..4247a5184 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -121,7 +121,7 @@ namespace Flow.Launcher.ViewModel public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public bool HasSettingControl => - PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins + !PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || // Is not JsonRPC plugin jsonRPCPluginBase.NeedCreateSettingPanel()); // Is JsonRPC plugin and need to create setting panel From 59e4fb82b97047bce46799d37d9df1e29e25a2b4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:49:24 +0800 Subject: [PATCH 036/125] Fix logic & Add code comments --- Flow.Launcher.Core/Plugin/PluginManager.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 3ba89fce6..c69af2327 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -725,9 +725,10 @@ namespace Flow.Launcher.Core.Plugin public static bool PluginModified(string id) { - // We should consider initializing plugin as modified since it cannot be installed/uninstalled/updated and - // we cannot call any plugin methods - return ModifiedPlugins.Contains(id) && _allInitializedPlugins.ContainsKey(id); + return ModifiedPlugins.Contains(id) || + // We should consider initializing plugin as modified since it cannot be installed/uninstalled/updated and + // we cannot call any plugin methods + _allInitializedPlugins.ContainsKey(id); } public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) From 067a51775e7222a65532f7c8eeee93e00e9d90de Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:54:20 +0800 Subject: [PATCH 037/125] Fix null exception --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2299e4e98..a7fb6ae44 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1327,7 +1327,10 @@ namespace Flow.Launcher.ViewModel private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - await _updateSource?.CancelAsync(); + if (_updateSource != null) + { + await _updateSource.CancelAsync(); + } App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1906,7 +1909,10 @@ namespace Flow.Launcher.ViewModel if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - await _dialogJumpSource?.CancelAsync(); + if (_dialogJumpSource != null) + { + await _dialogJumpSource.CancelAsync(); + } // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource(); From 63f86613c373373dacefeea9f7fb430e836442e6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:55:06 +0800 Subject: [PATCH 038/125] Use internal PluginModified method instead of API.PluginModified --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index c69af2327..d94ec8d1d 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -368,7 +368,7 @@ namespace Flow.Launcher.Core.Plugin if (dialogJump && plugin.Plugin is not IAsyncDialogJump) return Array.Empty(); - if (API.PluginModified(plugin.Metadata.ID)) + if (PluginModified(plugin.Metadata.ID)) return Array.Empty(); return [plugin]; From f808469285749ad7c22af03c3e30618da520d2e6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:59:28 +0800 Subject: [PATCH 039/125] Fix logic & Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 21 +++++++++++++++++---- Flow.Launcher/ViewModel/PluginViewModel.cs | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d94ec8d1d..d5acfcb55 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -533,7 +533,7 @@ namespace Flow.Launcher.Core.Plugin else { return [.. _allInitializedPlugins.Values - .Where(p => !IsInitFailed(p.Metadata.ID))]; + .Where(p => !_initFailedPlugins.ContainsKey(p.Metadata.ID))]; } } @@ -596,11 +596,24 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Check Init Failed + #region Check Initializing or Init Failed - public static bool IsInitFailed(string id) + public static bool IsInitializingOrInitFailed(string id) { - return _initFailedPlugins.ContainsKey(id); + // Id does not exist in loaded plugins + if (!_allLoadedPlugins.Any(x => x.Metadata.ID == id)) return false; + + // Plugin initialized already + if (_allInitializedPlugins.ContainsKey(id)) + { + // Check if the plugin initialization failed + return _initFailedPlugins.ContainsKey(id); + } + // Plugin is still initializing + else + { + return true; + } } #endregion diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 4247a5184..acd3902ab 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -121,7 +121,7 @@ namespace Flow.Launcher.ViewModel public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public bool HasSettingControl => - !PluginManager.IsInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for init failed plugins + !PluginManager.IsInitializingOrInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for initializing or init failed plugins PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || // Is not JsonRPC plugin jsonRPCPluginBase.NeedCreateSettingPanel()); // Is JsonRPC plugin and need to create setting panel From bef1feea0a3f7f15d5a32514b02aa6374371e17c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 18:05:22 +0800 Subject: [PATCH 040/125] Fix plugin page logic --- .../ViewModels/SettingsPanePluginsViewModel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index b7723276e..5ab3bd087 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -115,9 +115,10 @@ public partial class SettingsPanePluginsViewModel : BaseModel } private List? _pluginViewModels; - // Get all initialized plugins and ignore those that are not initialized - // Include those init failed plugins so that we can uninstall them - public List PluginViewModels => _pluginViewModels ??= PluginManager.GetAllInitializedPlugins(true) + // Get all plugins: Initializing & Initialized & Init failed plugins + // Include init failed ones so that we can uninstall them + // Include initializing ones so that we can change related settings like action keywords, etc. + public List PluginViewModels => _pluginViewModels ??= App.API.GetAllPlugins() .OrderBy(plugin => plugin.Metadata.Disabled) .ThenBy(plugin => plugin.Metadata.Name) .Select(plugin => new PluginViewModel From 566bd04e3fb0ec35c9541c2f1a1b957a07fe3a6e Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 18:06:05 +0800 Subject: [PATCH 041/125] Support two types in one class --- Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 756aa89c1..ea647491e 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -139,7 +139,7 @@ namespace Flow.Launcher.Infrastructure.DialogJump }; _dialogJumpExplorers.TryAdd(dialogJumpExplorer, null); } - else if (pair.Plugin is IDialogJumpDialog dialog) + if (pair.Plugin is IDialogJumpDialog dialog) { var dialogJumpDialog = new DialogJumpDialogPair { From 269d21a4c02202c2c48f8c96c579590474f9057d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 18:11:16 +0800 Subject: [PATCH 042/125] Change plugin modified logic --- Flow.Launcher.Core/Plugin/PluginManager.cs | 5 +---- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d5acfcb55..122542d23 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -738,10 +738,7 @@ namespace Flow.Launcher.Core.Plugin public static bool PluginModified(string id) { - return ModifiedPlugins.Contains(id) || - // We should consider initializing plugin as modified since it cannot be installed/uninstalled/updated and - // we cannot call any plugin methods - _allInitializedPlugins.ContainsKey(id); + return ModifiedPlugins.Contains(id); } public static async Task UpdatePluginAsync(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 456341a71..269635d5e 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -534,8 +534,7 @@ namespace Flow.Launcher.Plugin /// /// Check if the plugin has been modified. - /// If this plugin is initializing, it will be marked as modified. - /// Or if this plugin is updated, installed or uninstalled and users do not restart the app, + /// If this plugin is updated, installed or uninstalled and users do not restart the app, /// it will be marked as modified /// /// Plugin id From 0f8553b45d164556be8052c38c261e17280c43d3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 19:39:42 +0800 Subject: [PATCH 043/125] Refresh home page after plugins are initialized --- Flow.Launcher/App.xaml.cs | 7 +++++++ Flow.Launcher/PublicAPIInstance.cs | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 7bef6ae97..be017fc62 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -253,6 +253,13 @@ namespace Flow.Launcher await PluginManager.InitializePluginsAsync(_mainVM); + // Refresh home page after plugins are initialized because users may open main window during plugin initialization + // And home page is created without full plugin list + if (_settings.ShowHomePage && _mainVM.QueryResultsSelected() && string.IsNullOrEmpty(_mainVM.QueryText)) + { + _mainVM.QueryResults(); + } + AutoPluginUpdates(); // Save all settings since we possibly update the plugin environment paths diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index 69ca0c01d..40a582fb9 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -5,7 +5,6 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; using System.Runtime.CompilerServices; using System.Threading; From e0240784b502405d4b39de5daaa66b6b1fe38496 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 21:11:48 +0800 Subject: [PATCH 044/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 80 +++++++++++----------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 9d511297e..8b8a14723 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -55,7 +55,7 @@ namespace Flow.Launcher.Core.Plugin return plugins; } - private static IEnumerable DotNetPlugins(List source) + private static List DotNetPlugins(List source) { var erroredPlugins = new List(); @@ -65,54 +65,54 @@ namespace Flow.Launcher.Core.Plugin foreach (var metadata in metadatas) { var milliseconds = API.StopwatchLogDebug(ClassName, $"Constructor init cost for {metadata.Name}", () => + { + Assembly assembly = null; + IAsyncPlugin plugin = null; + + try { - Assembly assembly = null; - IAsyncPlugin plugin = null; + var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); + assembly = assemblyLoader.LoadAssemblyAndDependencies(); - try - { - var assemblyLoader = new PluginAssemblyLoader(metadata.ExecuteFilePath); - assembly = assemblyLoader.LoadAssemblyAndDependencies(); + var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, + typeof(IAsyncPlugin)); - var type = assemblyLoader.FromAssemblyGetTypeOfInterface(assembly, - typeof(IAsyncPlugin)); + plugin = Activator.CreateInstance(type) as IAsyncPlugin; - plugin = Activator.CreateInstance(type) as IAsyncPlugin; - - metadata.AssemblyName = assembly.GetName().Name; - } + metadata.AssemblyName = assembly.GetName().Name; + } #if DEBUG - catch (Exception) - { - throw; - } + catch (Exception) + { + throw; + } #else - catch (Exception e) when (assembly == null) - { - Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); - } - catch (InvalidOperationException e) - { - Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); - } - catch (ReflectionTypeLoadException e) - { - Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); - } - catch (Exception e) - { - Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); - } + catch (Exception e) when (assembly == null) + { + Log.Exception(ClassName, $"Couldn't load assembly for the plugin: {metadata.Name}", e); + } + catch (InvalidOperationException e) + { + Log.Exception(ClassName, $"Can't find the required IPlugin interface for the plugin: <{metadata.Name}>", e); + } + catch (ReflectionTypeLoadException e) + { + Log.Exception(ClassName, $"The GetTypes method was unable to load assembly types for the plugin: <{metadata.Name}>", e); + } + catch (Exception e) + { + Log.Exception(ClassName, $"The following plugin has errored and can not be loaded: <{metadata.Name}>", e); + } #endif - if (plugin == null) - { - erroredPlugins.Add(metadata.Name); - return; - } + if (plugin == null) + { + erroredPlugins.Add(metadata.Name); + return; + } - plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata }); - }); + plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata }); + }); metadata.InitTime += milliseconds; } From 3221f930c4240af5748ffee1d233f9fda64d2114 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 21:17:30 +0800 Subject: [PATCH 045/125] Add info log message for plugin constructors --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 8b8a14723..381772ea8 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -113,7 +113,9 @@ namespace Flow.Launcher.Core.Plugin plugins.Add(new PluginPair { Plugin = plugin, Metadata = metadata }); }); + metadata.InitTime += milliseconds; + API.LogInfo(ClassName, $"Constructor cost for <{metadata.Name}> is <{metadata.InitTime}ms>"); } if (erroredPlugins.Count > 0) From 93af07931681588e68312907e7c1ab0427938b4a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 12 Aug 2025 17:49:22 +0800 Subject: [PATCH 046/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 74 +++++++++++----------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 122542d23..2823ad132 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -485,6 +485,43 @@ namespace Flow.Launcher.Core.Plugin #endregion + #region Get Plugin List + + public static List GetAllLoadedPlugins() + { + return [.. _allLoadedPlugins]; + } + + public static List GetAllInitializedPlugins(bool includeFailed) + { + if (includeFailed) + { + return [.. _allInitializedPlugins.Values]; + } + else + { + return [.. _allInitializedPlugins.Values + .Where(p => !_initFailedPlugins.ContainsKey(p.Metadata.ID))]; + } + } + + public static List GetGlobalPlugins() + { + return [.. _globalPlugins.Values]; + } + + public static Dictionary GetNonGlobalPlugins() + { + return _nonGlobalPlugins.ToDictionary(); + } + + public static List GetTranslationPlugins() + { + return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; + } + + #endregion + #region Update Metadata & Get Plugin public static void UpdatePluginMetadata(IReadOnlyList results, PluginMetadata metadata, Query query) @@ -517,43 +554,6 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Get Plugin List - - public static List GetAllLoadedPlugins() - { - return [.. _allLoadedPlugins]; - } - - public static List GetAllInitializedPlugins(bool containFailed) - { - if (containFailed) - { - return [.. _allInitializedPlugins.Values]; - } - else - { - return [.. _allInitializedPlugins.Values - .Where(p => !_initFailedPlugins.ContainsKey(p.Metadata.ID))]; - } - } - - public static List GetGlobalPlugins() - { - return [.. _globalPlugins.Values]; - } - - public static Dictionary GetNonGlobalPlugins() - { - return _nonGlobalPlugins.ToDictionary(); - } - - public static List GetTranslationPlugins() - { - return [.. _translationPlugins.Where(p => !PluginModified(p.Metadata.ID))]; - } - - #endregion - #region Get Context Menus public static List GetContextMenusForPlugin(Result result) From f08466245a6dec8751c16527fc0061f2bc2dbda0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 12 Aug 2025 17:53:53 +0800 Subject: [PATCH 047/125] Set GetAllInitializedPlugins to private --- Flow.Launcher.Core/Plugin/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 2823ad132..22a3e8c06 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -492,7 +492,7 @@ namespace Flow.Launcher.Core.Plugin return [.. _allLoadedPlugins]; } - public static List GetAllInitializedPlugins(bool includeFailed) + private static List GetAllInitializedPlugins(bool includeFailed) { if (includeFailed) { From 31c8e850cdef89995b68ca61e83f473527e6575a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 12 Aug 2025 18:03:23 +0800 Subject: [PATCH 048/125] Improve code quality --- Flow.Launcher.Core/Plugin/PluginManager.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 22a3e8c06..ac104c011 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -914,13 +914,19 @@ namespace Flow.Launcher.Core.Plugin string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); - _allInitializedPlugins.TryRemove(plugin.ID, out var item); - _initFailedPlugins.TryRemove(plugin.ID, out var item1); - _globalPlugins.TryRemove(plugin.ID, out var item2); + { + _allInitializedPlugins.TryRemove(plugin.ID, out var _); + } + { + _initFailedPlugins.TryRemove(plugin.ID, out var _); + } + { + _globalPlugins.TryRemove(plugin.ID, out var _); + } var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); foreach (var key in keysToRemove) { - _nonGlobalPlugins.Remove(key, out var item3); + _nonGlobalPlugins.Remove(key, out var ite3); } } From 9221435bad450eede47ab030b5d96024b5b8ba27 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 12 Aug 2025 18:07:10 +0800 Subject: [PATCH 049/125] Remove plugin from _allLoadedPlugins when one plugin is uninstalled --- Flow.Launcher.Core/Plugin/PluginManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index ac104c011..d13e4c3b0 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -914,6 +914,9 @@ namespace Flow.Launcher.Core.Plugin string.Format(API.GetTranslation("failedToRemovePluginCacheMessage"), plugin.Name)); } Settings.RemovePluginSettings(plugin.ID); + { + _allLoadedPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + } { _allInitializedPlugins.TryRemove(plugin.ID, out var _); } From 04bd9ddc2c1dbd96e65dc99dbfe4edc1173551be Mon Sep 17 00:00:00 2001 From: Jack Ye <1160210343@qq.com> Date: Tue, 12 Aug 2025 18:09:59 +0800 Subject: [PATCH 050/125] Verify File.Delete exception handling Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d13e4c3b0..37b2660dc 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -183,7 +183,14 @@ namespace Flow.Launcher.Core.Plugin const string binding = "flowlauncher.py"; foreach (var subDirectory in Directory.GetDirectories(DataLocation.PluginsDirectory)) { - File.Delete(Path.Combine(subDirectory, binding)); + try + { + File.Delete(Path.Combine(subDirectory, binding)); + } + catch (Exception e) + { + API.LogDebug(ClassName, $"Failed to delete {binding} in {subDirectory}: {e.Message}"); + } } } From fb8daa4ed90f0ef340e00f1b912453521a5e6fe3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 12 Aug 2025 18:12:15 +0800 Subject: [PATCH 051/125] Potential race condition in action keyword management --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 37b2660dc..b0624f373 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -648,14 +648,7 @@ namespace Flow.Launcher.Core.Plugin } else { - if (_nonGlobalPlugins.TryGetValue(newActionKeyword, out var item)) - { - _nonGlobalPlugins.TryUpdate(newActionKeyword, plugin, item); - } - else - { - _nonGlobalPlugins.TryAdd(newActionKeyword, plugin); - } + _nonGlobalPlugins.AddOrUpdate(newActionKeyword, plugin, (key, oldValue) => plugin); } // Update action keywords and action keyword in plugin metadata From 3cc4f13f4d60d0837edf4020e474a7710589953b Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 1 Oct 2025 23:05:41 -0300 Subject: [PATCH 052/125] feat: New option to show executed results in settings --- .../UserSettings/Settings.cs | 15 +++++++++ Flow.Launcher/Languages/en.xaml | 1 + .../Views/SettingsPaneGeneral.xaml | 31 +++++++++++++------ Flow.Launcher/Storage/ExecutedHistory.cs | 21 +++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 Flow.Launcher/Storage/ExecutedHistory.cs diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 23f9047fe..231e8cc0f 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -230,6 +230,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } + + private bool _showHistoryExecutedResultsForHomePage = false; + public bool ShowHistoryExecutedResultsForHomePage + { + get => _showHistoryExecutedResultsForHomePage; + set + { + if (_showHistoryExecutedResultsForHomePage != value) + { + _showHistoryExecutedResultsForHomePage = value; + OnPropertyChanged(); + } + } + } + public int MaxHistoryResultsToShowForHomePage { get; set; } = 5; public bool AutoRestartAfterChanging { get; set; } = false; diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index f7fd0c8e5..0fd1af78d 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -165,6 +165,7 @@ Home Page Show home page results when query text is empty. Show History Results in Home Page + Show History Executed Results in Home Page Maximum History Results Shown in Home Page This can only be edited if plugin supports Home feature and Home Page is enabled. Show Search Window at Foremost diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index 81e15df69..cfbe8c6c1 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -381,18 +381,31 @@ OnContent="{DynamicResource enable}" /> - + + + + + + + Items { get; private set; } = new List(); + private int _maxHistory = 300; + + public void Add(Result result) + { + Items.Add(result); + } + +} From c0369e6e7672ddacffa1a91ac7eb660b48aba998 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Thu, 2 Oct 2025 00:03:48 -0300 Subject: [PATCH 053/125] feat: radio button with history query option or history executed option --- .../UserSettings/Settings.cs | 20 +++++-- Flow.Launcher/Languages/en.xaml | 6 ++- Flow.Launcher/MainWindow.xaml.cs | 2 +- .../Views/SettingsPaneGeneral.xaml | 53 ++++++++----------- Flow.Launcher/Storage/ExecutedHistory.cs | 30 +++++++++-- Flow.Launcher/Storage/ExecutedHistoryItem.cs | 17 ++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 51 +++++++++++++++++- 7 files changed, 134 insertions(+), 45 deletions(-) create mode 100644 Flow.Launcher/Storage/ExecutedHistoryItem.cs diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 231e8cc0f..08035ee44 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -216,16 +216,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } - private bool _showHistoryResultsForHomePage = false; - public bool ShowHistoryResultsForHomePage + private bool _showHistoryQueryResultsForHomePage = false; + public bool ShowHistoryQueryResultsForHomePage { - get => _showHistoryResultsForHomePage; + get => _showHistoryQueryResultsForHomePage; set { - if (_showHistoryResultsForHomePage != value) + if (_showHistoryQueryResultsForHomePage != value) { - _showHistoryResultsForHomePage = value; + _showHistoryQueryResultsForHomePage = value; OnPropertyChanged(); + if (value && _showHistoryExecutedResultsForHomePage) + { + _showHistoryExecutedResultsForHomePage = false; + OnPropertyChanged(nameof(ShowHistoryExecutedResultsForHomePage)); + } } } } @@ -241,6 +246,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings { _showHistoryExecutedResultsForHomePage = value; OnPropertyChanged(); + if (value && _showHistoryQueryResultsForHomePage) + { + _showHistoryQueryResultsForHomePage = false; + OnPropertyChanged(nameof(ShowHistoryQueryResultsForHomePage)); + } } } } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 0fd1af78d..abadab964 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -164,7 +164,11 @@ Please check your system registry access or contact support. Home Page Show home page results when query text is empty. - Show History Results in Home Page + Home Page History + Choose the type of history to show on the home page + Query History + Executed History + Show History Query Results in Home Page Show History Executed Results in Home Page Maximum History Results Shown in Home Page This can only be edited if plugin supports Home feature and Home Page is enabled. diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 7b6a0d79b..e6696c34c 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -317,7 +317,7 @@ namespace Flow.Launcher InitializeContextMenu(); break; case nameof(Settings.ShowHomePage): - case nameof(Settings.ShowHistoryResultsForHomePage): + case nameof(Settings.ShowHistoryQueryResultsForHomePage): if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText)) { _viewModel.QueryResults(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index cfbe8c6c1..9fe45e89e 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -373,39 +373,30 @@ OnContent="{DynamicResource enable}" /> - - - - - - - - - + + + + + + + + + + + - - - - Items { get; private set; } = new List(); - private int _maxHistory = 300; + [JsonInclude] public List Items { get; private set; } = []; + private const int MaxHistory = 300; public void Add(Result result) { - Items.Add(result); - } + var item = new ExecutedHistoryItem + { + Title = result.Title, + SubTitle = result.SubTitle, + IcoPath = result.IcoPath ?? string.Empty, + PluginID = result.PluginID, + OriginQuery = result.OriginQuery, + ExecutedDateTime = DateTime.Now + }; + var existing = Items.FirstOrDefault(x => x.OriginQuery.RawQuery == item.OriginQuery.RawQuery && x.PluginID == item.PluginID); + if (existing != null) + { + Items.Remove(existing); + } + + Items.Add(item); + + if (Items.Count > MaxHistory) + { + Items.RemoveAt(0); + } + } } diff --git a/Flow.Launcher/Storage/ExecutedHistoryItem.cs b/Flow.Launcher/Storage/ExecutedHistoryItem.cs new file mode 100644 index 000000000..8ae6d3336 --- /dev/null +++ b/Flow.Launcher/Storage/ExecutedHistoryItem.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Storage; +public class ExecutedHistoryItem +{ + public string Title { get; set; } = string.Empty; + public string SubTitle { get; set; } = string.Empty; + public string IcoPath { get; set; } = string.Empty; + public string PluginID { get; set; } = string.Empty; + public Query OriginQuery { get; set; } = null!; + public DateTime ExecutedDateTime { get; set; } +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d492f28c5..abdd239e9 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -41,9 +41,11 @@ namespace Flow.Launcher.ViewModel private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results private readonly FlowLauncherJsonStorage _historyItemsStorage; + private readonly FlowLauncherJsonStorage _executedHistoryStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; private readonly History _history; + private readonly ExecutedHistory _executedHistory; private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; @@ -148,9 +150,11 @@ namespace Flow.Launcher.ViewModel }; _historyItemsStorage = new FlowLauncherJsonStorage(); + _executedHistoryStorage = new FlowLauncherJsonStorage(); _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); _history = _historyItemsStorage.Load(); + _executedHistory = _executedHistoryStorage.Load(); _userSelectedRecord = _userSelectedRecordStorage.Load(); ContextMenu = new ResultsViewModel(Settings, this) @@ -529,6 +533,9 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { + if(Settings.ShowHistoryExecutedResultsForHomePage) + _executedHistory.Add(result); + _userSelectedRecord.Add(result); _history.Add(result.OriginQuery.RawQuery); lastHistoryIndex = 1; @@ -1448,10 +1455,14 @@ namespace Flow.Launcher.ViewModel }).ToArray(); // Query history results for home page firstly so it will be put on top of the results - if (Settings.ShowHistoryResultsForHomePage) + if (Settings.ShowHistoryQueryResultsForHomePage) { QueryHistoryTask(currentCancellationToken); } + else if (Settings.ShowHistoryExecutedResultsForHomePage) + { + QueryExecutedHistoryTask(currentCancellationToken); + } } else { @@ -1574,6 +1585,41 @@ namespace Flow.Launcher.ViewModel App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } } + + void QueryExecutedHistoryTask(CancellationToken token) + { + var historyItems = _executedHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + + var results = new List(); + foreach (var item in historyItems) + { + var result = new Result + { + Title = item.Title, + SubTitle = item.SubTitle, + IcoPath = item.IcoPath, + PluginID = item.PluginID, + OriginQuery = item.OriginQuery, + Action = _ => + { + App.API.ChangeQuery(item.OriginQuery.RawQuery); + return false; + }, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") + }; + results.Add(result); + } + + if (token.IsCancellationRequested) return; + + App.API.LogDebug(ClassName, "Update results for executed history"); + + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, + token, reSelect))) + { + App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); + } + } } private async Task ConstructQueryAsync(string queryText, IEnumerable customShortcuts, @@ -1698,7 +1744,7 @@ namespace Flow.Launcher.ViewModel /// True if existing results should be cleared, false otherwise. private bool ShouldClearExistingResultsForNonQuery(ICollection plugins) { - if (!Settings.ShowHistoryResultsForHomePage && (plugins.Count == 0 || plugins.All(x => x.Metadata.HomeDisabled == true))) + if (!Settings.ShowHistoryQueryResultsForHomePage && (plugins.Count == 0 || plugins.All(x => x.Metadata.HomeDisabled == true))) { App.API.LogDebug(ClassName, $"Existing results should be cleared for non-query"); return true; @@ -2163,6 +2209,7 @@ namespace Flow.Launcher.ViewModel public void Save() { _historyItemsStorage.Save(); + _executedHistoryStorage.Save(); _userSelectedRecordStorage.Save(); _topMostRecord.Save(); } From bc1f9d3734c666c7e18c53a69dd7b86a002858f7 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Thu, 2 Oct 2025 00:09:28 -0300 Subject: [PATCH 054/125] refactor: renaming executed history to last opened history --- .../UserSettings/Settings.cs | 16 ++++++------- Flow.Launcher/Languages/en.xaml | 4 ++-- .../Views/SettingsPaneGeneral.xaml | 4 ++-- ...xecutedHistory.cs => LastOpenedHistory.cs} | 6 ++--- ...istoryItem.cs => LastOpenedHistoryItem.cs} | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 24 +++++++++---------- 6 files changed, 28 insertions(+), 28 deletions(-) rename Flow.Launcher/Storage/{ExecutedHistory.cs => LastOpenedHistory.cs} (85%) rename Flow.Launcher/Storage/{ExecutedHistoryItem.cs => LastOpenedHistoryItem.cs} (93%) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 08035ee44..e33bf04ac 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -226,25 +226,25 @@ namespace Flow.Launcher.Infrastructure.UserSettings { _showHistoryQueryResultsForHomePage = value; OnPropertyChanged(); - if (value && _showHistoryExecutedResultsForHomePage) + if (value && _showHistoryLastOpenedResultsForHomePage) { - _showHistoryExecutedResultsForHomePage = false; - OnPropertyChanged(nameof(ShowHistoryExecutedResultsForHomePage)); + _showHistoryLastOpenedResultsForHomePage = false; + OnPropertyChanged(nameof(ShowHistoryLastOpenedResultsForHomePage)); } } } } - private bool _showHistoryExecutedResultsForHomePage = false; - public bool ShowHistoryExecutedResultsForHomePage + private bool _showHistoryLastOpenedResultsForHomePage = false; + public bool ShowHistoryLastOpenedResultsForHomePage { - get => _showHistoryExecutedResultsForHomePage; + get => _showHistoryLastOpenedResultsForHomePage; set { - if (_showHistoryExecutedResultsForHomePage != value) + if (_showHistoryLastOpenedResultsForHomePage != value) { - _showHistoryExecutedResultsForHomePage = value; + _showHistoryLastOpenedResultsForHomePage = value; OnPropertyChanged(); if (value && _showHistoryQueryResultsForHomePage) { diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index abadab964..14677e1ea 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -167,9 +167,9 @@ Home Page History Choose the type of history to show on the home page Query History - Executed History + Last Opened History Show History Query Results in Home Page - Show History Executed Results in Home Page + Show History Last Opened Results in Home Page Maximum History Results Shown in Home Page This can only be edited if plugin supports Home feature and Home Page is enabled. Show Search Window at Foremost diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index 9fe45e89e..e694a84d8 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -377,8 +377,8 @@ - diff --git a/Flow.Launcher/Storage/ExecutedHistory.cs b/Flow.Launcher/Storage/LastOpenedHistory.cs similarity index 85% rename from Flow.Launcher/Storage/ExecutedHistory.cs rename to Flow.Launcher/Storage/LastOpenedHistory.cs index d9b9966af..6394e2ce5 100644 --- a/Flow.Launcher/Storage/ExecutedHistory.cs +++ b/Flow.Launcher/Storage/LastOpenedHistory.cs @@ -8,14 +8,14 @@ using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; namespace Flow.Launcher.Storage; -public class ExecutedHistory +public class LastOpenedHistory { - [JsonInclude] public List Items { get; private set; } = []; + [JsonInclude] public List Items { get; private set; } = []; private const int MaxHistory = 300; public void Add(Result result) { - var item = new ExecutedHistoryItem + var item = new LastOpenedHistoryItem { Title = result.Title, SubTitle = result.SubTitle, diff --git a/Flow.Launcher/Storage/ExecutedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs similarity index 93% rename from Flow.Launcher/Storage/ExecutedHistoryItem.cs rename to Flow.Launcher/Storage/LastOpenedHistoryItem.cs index 8ae6d3336..28bd49a95 100644 --- a/Flow.Launcher/Storage/ExecutedHistoryItem.cs +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; -public class ExecutedHistoryItem +public class LastOpenedHistoryItem { public string Title { get; set; } = string.Empty; public string SubTitle { get; set; } = string.Empty; diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index abdd239e9..2e3e8d4d7 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -41,11 +41,11 @@ namespace Flow.Launcher.ViewModel private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results private readonly FlowLauncherJsonStorage _historyItemsStorage; - private readonly FlowLauncherJsonStorage _executedHistoryStorage; + private readonly FlowLauncherJsonStorage _lastOpenedHistoryStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; private readonly History _history; - private readonly ExecutedHistory _executedHistory; + private readonly LastOpenedHistory _lastOpenedHistory; private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; @@ -150,11 +150,11 @@ namespace Flow.Launcher.ViewModel }; _historyItemsStorage = new FlowLauncherJsonStorage(); - _executedHistoryStorage = new FlowLauncherJsonStorage(); + _lastOpenedHistoryStorage = new FlowLauncherJsonStorage(); _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); _history = _historyItemsStorage.Load(); - _executedHistory = _executedHistoryStorage.Load(); + _lastOpenedHistory = _lastOpenedHistoryStorage.Load(); _userSelectedRecord = _userSelectedRecordStorage.Load(); ContextMenu = new ResultsViewModel(Settings, this) @@ -533,8 +533,8 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { - if(Settings.ShowHistoryExecutedResultsForHomePage) - _executedHistory.Add(result); + if(Settings.ShowHistoryLastOpenedResultsForHomePage) + _lastOpenedHistory.Add(result); _userSelectedRecord.Add(result); _history.Add(result.OriginQuery.RawQuery); @@ -1459,9 +1459,9 @@ namespace Flow.Launcher.ViewModel { QueryHistoryTask(currentCancellationToken); } - else if (Settings.ShowHistoryExecutedResultsForHomePage) + else if (Settings.ShowHistoryLastOpenedResultsForHomePage) { - QueryExecutedHistoryTask(currentCancellationToken); + QueryLastOpenedHistoryTask(currentCancellationToken); } } else @@ -1586,9 +1586,9 @@ namespace Flow.Launcher.ViewModel } } - void QueryExecutedHistoryTask(CancellationToken token) + void QueryLastOpenedHistoryTask(CancellationToken token) { - var historyItems = _executedHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _lastOpenedHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = new List(); foreach (var item in historyItems) @@ -1612,7 +1612,7 @@ namespace Flow.Launcher.ViewModel if (token.IsCancellationRequested) return; - App.API.LogDebug(ClassName, "Update results for executed history"); + App.API.LogDebug(ClassName, "Update results for last opened history"); if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, token, reSelect))) @@ -2209,7 +2209,7 @@ namespace Flow.Launcher.ViewModel public void Save() { _historyItemsStorage.Save(); - _executedHistoryStorage.Save(); + _lastOpenedHistoryStorage.Save(); _userSelectedRecordStorage.Save(); _topMostRecord.Save(); } From 5fcd01224c3bc28fd0f6566c5c49a5c22f6a2106 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Thu, 2 Oct 2025 00:19:40 -0300 Subject: [PATCH 055/125] feat: text --- Flow.Launcher/Languages/en.xaml | 6 +++--- Flow.Launcher/Storage/LastOpenedHistory.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 14677e1ea..7fd10272a 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -168,9 +168,9 @@ Choose the type of history to show on the home page Query History Last Opened History - Show History Query Results in Home Page - Show History Last Opened Results in Home Page - Maximum History Results Shown in Home Page + Show Query History + Show Last Opened History + Maximum History Results Shown This can only be edited if plugin supports Home feature and Home Page is enabled. Show Search Window at Foremost Overrides other programs' 'Always on Top' setting and displays Flow in the foremost position. diff --git a/Flow.Launcher/Storage/LastOpenedHistory.cs b/Flow.Launcher/Storage/LastOpenedHistory.cs index 6394e2ce5..19f30855e 100644 --- a/Flow.Launcher/Storage/LastOpenedHistory.cs +++ b/Flow.Launcher/Storage/LastOpenedHistory.cs @@ -25,6 +25,7 @@ public class LastOpenedHistory ExecutedDateTime = DateTime.Now }; + // Aparenta estar duplicando... var existing = Items.FirstOrDefault(x => x.OriginQuery.RawQuery == item.OriginQuery.RawQuery && x.PluginID == item.PluginID); if (existing != null) { From 5ed94c8abc9270d8a55f3f4fd79b1c02ac146ec7 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 20:25:45 +0800 Subject: [PATCH 056/125] Resolve conflicts --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 +++------ Flow.Launcher.Core/Resource/Internationalization.cs | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index c5f064bc6..377b68448 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -184,7 +184,7 @@ namespace Flow.Launcher.Core.Plugin } catch (Exception e) { - API.LogDebug(ClassName, $"Failed to delete {binding} in {subDirectory}: {e.Message}"); + PublicApi.Instance.LogDebug(ClassName, $"Failed to delete {binding} in {subDirectory}: {e.Message}"); } } } @@ -300,11 +300,8 @@ namespace Flow.Launcher.Core.Plugin { var failed = string.Join(",", _initFailedPlugins.Values.Select(x => x.Metadata.Name)); PublicApi.Instance.ShowMsg( - PublicApi.Instance.GetTranslation("failedToInitializePluginsTitle"), - string.Format( - API.GetTranslation("failedToInitializePluginsMessage"), - failed - ), + Localize.failedToInitializePluginsTitle(), + Localize.failedToInitializePluginsMessage(failed), "", false ); diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 7e0aaa141..7505dca62 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -389,7 +389,7 @@ namespace Flow.Launcher.Core.Resource } catch (Exception e) { - API.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); + PublicApi.Instance.LogException(ClassName, $"Failed for <{p.Metadata.Name}>", e); } } From 66fb1d7c60b004d849347a5bc046c084ba9f422d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 21:52:31 +0800 Subject: [PATCH 057/125] Register plugin action keywords when plugins are loaded --- Flow.Launcher.Core/Plugin/PluginManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 377b68448..deb474219 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -246,6 +246,9 @@ namespace Flow.Launcher.Core.Plugin { var initTasks = _allLoadedPlugins.Select(pair => Task.Run(async () => { + // Register plugin action keywords so that plugins can be queried in results + RegisterPluginActionKeywords(pair); + try { var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Init method time cost for <{pair.Metadata.Name}>", @@ -284,9 +287,6 @@ namespace Flow.Launcher.Core.Plugin // Update plugin metadata translation after the plugin is initialized with IPublicAPI instance Internationalization.UpdatePluginMetadataTranslation(pair); - // Register plugin action keywords so that plugins can be queried in results - RegisterPluginActionKeywords(pair); - // Add plugin to Dialog Jump plugin list after the plugin is initialized DialogJump.InitializeDialogJumpPlugin(pair); From 9a20b0e0de5d982e9f8b387fdf66fbc971189c2a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 21:59:05 +0800 Subject: [PATCH 058/125] Expose initialized plugins via PublicAPI --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 11 ++++++++++- Flow.Launcher/PublicAPIInstance.cs | 3 +++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index deb474219..71234eb18 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -491,7 +491,7 @@ namespace Flow.Launcher.Core.Plugin return [.. _allLoadedPlugins]; } - private static List GetAllInitializedPlugins(bool includeFailed) + public static List GetAllInitializedPlugins(bool includeFailed) { if (includeFailed) { @@ -504,7 +504,7 @@ namespace Flow.Launcher.Core.Plugin } } - public static List GetGlobalPlugins() + private static List GetGlobalPlugins() { return [.. _globalPlugins.Values]; } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index b25f04a7e..f156979c6 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -179,6 +179,15 @@ namespace Flow.Launcher.Plugin /// List GetAllPlugins(); + /// + /// Get all initialized plugins + /// + /// + /// Whether to include plugins that failed to initialize + /// + /// + List GetAllInitializedPlugins(bool includeFailed); + /// /// Registers a callback function for global keyboard events. /// diff --git a/Flow.Launcher/PublicAPIInstance.cs b/Flow.Launcher/PublicAPIInstance.cs index f1c2beefd..ff10d2d48 100644 --- a/Flow.Launcher/PublicAPIInstance.cs +++ b/Flow.Launcher/PublicAPIInstance.cs @@ -249,6 +249,9 @@ namespace Flow.Launcher public List GetAllPlugins() => PluginManager.GetAllLoadedPlugins(); + public List GetAllInitializedPlugins(bool includeFailed) => + PluginManager.GetAllInitializedPlugins(includeFailed); + public MatchResult FuzzySearch(string query, string stringToCompare) => StringMatcher.FuzzySearch(query, stringToCompare); From 297cb5c3efc1b7d09d60719713c4348db0e7fd5a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 22:23:07 +0800 Subject: [PATCH 059/125] Return results to tell users that this plugin is still initializing --- Flow.Launcher.Core/Plugin/PluginManager.cs | 55 ++++++++++++++++++++++ Flow.Launcher/Languages/en.xaml | 2 + 2 files changed, 57 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 71234eb18..e76306f58 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -383,6 +383,28 @@ namespace Flow.Launcher.Core.Plugin var results = new List(); var metadata = pair.Metadata; + if (IsPluginInitializing(metadata)) + { + Result r = new() + { + Title = Localize.pluginStillInitializing(metadata.Name), + SubTitle = Localize.pluginStillInitializingSubtitle(), + IcoPath = metadata.IcoPath, + PluginDirectory = metadata.PluginDirectory, + ActionKeywordAssigned = query.ActionKeyword, + PluginID = metadata.ID, + OriginQuery = query, + Action = _ => + { + PublicApi.Instance.ReQuery(); + return false; + }, + Score = -100 + }; + results.Add(r); + return results; + } + try { var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", @@ -427,6 +449,28 @@ namespace Flow.Launcher.Core.Plugin var results = new List(); var metadata = pair.Metadata; + if (IsPluginInitializing(metadata)) + { + Result r = new() + { + Title = Localize.pluginStillInitializing(metadata.Name), + SubTitle = Localize.pluginStillInitializingSubtitle(), + IcoPath = metadata.IcoPath, + PluginDirectory = metadata.PluginDirectory, + ActionKeywordAssigned = query.ActionKeyword, + PluginID = metadata.ID, + OriginQuery = query, + Action = _ => + { + PublicApi.Instance.ReQuery(); + return false; + }, + Score = -100 + }; + results.Add(r); + return results; + } + try { var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", @@ -457,6 +501,12 @@ namespace Flow.Launcher.Core.Plugin var results = new List(); var metadata = pair.Metadata; + if (IsPluginInitializing(metadata)) + { + // null will be fine since the results will only be added into queue if the token hasn't been cancelled + return null; + } + try { var milliseconds = await PublicApi.Instance.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", @@ -482,6 +532,11 @@ namespace Flow.Launcher.Core.Plugin return results; } + private static bool IsPluginInitializing(PluginMetadata metadata) + { + return !_allInitializedPlugins.ContainsKey(metadata.ID); + } + #endregion #region Get Plugin List diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a51782f40..9ac0b8cb0 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -66,6 +66,8 @@ Position Reset Reset search window position Type here to search + {0}: This plugin is still initializing! + Please wait for a while and select this result to requery Settings From 2be10eb4cae3423c75b860492b4a20dde149161c Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 2 Oct 2025 22:26:28 +0800 Subject: [PATCH 060/125] Add translation for plugin failed to respond & Improve translations for plugin stil initializing --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- Flow.Launcher/Languages/en.xaml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index e76306f58..e00aa3312 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -429,8 +429,8 @@ namespace Flow.Launcher.Core.Plugin { Result r = new() { - Title = $"{metadata.Name}: Failed to respond!", - SubTitle = "Select this result for more info", + Title = Localize.pluginFailedToRespond(metadata.Name), + SubTitle = Localize.pluginFailedToRespondSubtitle(), IcoPath = Constant.ErrorIcon, PluginDirectory = metadata.PluginDirectory, ActionKeywordAssigned = query.ActionKeyword, diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 9ac0b8cb0..48e1d9225 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -67,7 +67,9 @@ Reset search window position Type here to search {0}: This plugin is still initializing! - Please wait for a while and select this result to requery + Select this result to requery + {0}: Failed to respond! + Select this result for more info Settings From 76cc22d5af9c4ffc620eaa74e262cadf69d14bfb Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 15:21:11 +0800 Subject: [PATCH 061/125] Use TryRemove and discard out var to clean up action keywords --- Flow.Launcher.Core/Plugin/PluginManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index e00aa3312..a715d5b8b 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -727,7 +727,7 @@ namespace Flow.Launcher.Core.Plugin if (oldActionkeyword != Query.GlobalPluginWildcardSign) { - _nonGlobalPlugins.TryRemove(oldActionkeyword, out var item); + _nonGlobalPlugins.TryRemove(oldActionkeyword, out _); } // Update action keywords and action keyword in plugin metadata @@ -976,7 +976,7 @@ namespace Flow.Launcher.Core.Plugin var keysToRemove = _nonGlobalPlugins.Where(p => p.Value.Metadata.ID == plugin.ID).Select(p => p.Key).ToList(); foreach (var key in keysToRemove) { - _nonGlobalPlugins.Remove(key, out var ite3); + _nonGlobalPlugins.TryRemove(key, out var _); } } From 171eb2dd8ba452f588ac6d05e7cf1deb1c02cedd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 15:28:11 +0800 Subject: [PATCH 062/125] Data race on _allLoadedPlugins (List) across threads --- Flow.Launcher.Core/Plugin/PluginManager.cs | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index a715d5b8b..b93fe3728 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -25,7 +25,7 @@ namespace Flow.Launcher.Core.Plugin { private static readonly string ClassName = nameof(PluginManager); - private static List _allLoadedPlugins; + private static readonly ConcurrentDictionary _allLoadedPlugins = []; private static readonly ConcurrentDictionary _allInitializedPlugins = []; private static readonly ConcurrentDictionary _initFailedPlugins = []; private static readonly ConcurrentDictionary _globalPlugins = []; @@ -204,7 +204,17 @@ namespace Flow.Launcher.Core.Plugin Settings.UpdatePluginSettings(metadatas); // Load plugins - _allLoadedPlugins = PluginsLoader.Plugins(metadatas, Settings); + var allLoadedPlugins = PluginsLoader.Plugins(metadatas, Settings); + foreach (var plugin in allLoadedPlugins) + { + if (plugin != null) + { + if (!_allLoadedPlugins.TryAdd(plugin.Metadata.ID, plugin)) + { + PublicApi.Instance.LogError(ClassName, $"Plugin with ID {plugin.Metadata.ID} already loaded"); + } + } + } // Since dotnet plugins need to get assembly name first, we should update plugin directory after loading plugins UpdatePluginDirectory(metadatas); @@ -244,8 +254,10 @@ namespace Flow.Launcher.Core.Plugin /// return the list of failed to init plugins or null for none public static async Task InitializePluginsAsync(IResultUpdateRegister register) { - var initTasks = _allLoadedPlugins.Select(pair => Task.Run(async () => + var initTasks = _allLoadedPlugins.Select(x => Task.Run(async () => { + var pair = x.Value; + // Register plugin action keywords so that plugins can be queried in results RegisterPluginActionKeywords(pair); @@ -543,7 +555,7 @@ namespace Flow.Launcher.Core.Plugin public static List GetAllLoadedPlugins() { - return [.. _allLoadedPlugins]; + return [.. _allLoadedPlugins.Values]; } public static List GetAllInitializedPlugins(bool includeFailed) @@ -655,7 +667,7 @@ namespace Flow.Launcher.Core.Plugin public static bool IsInitializingOrInitFailed(string id) { // Id does not exist in loaded plugins - if (!_allLoadedPlugins.Any(x => x.Metadata.ID == id)) return false; + if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; // Plugin initialized already if (_allInitializedPlugins.ContainsKey(id)) @@ -962,7 +974,7 @@ namespace Flow.Launcher.Core.Plugin } Settings.RemovePluginSettings(plugin.ID); { - _allLoadedPlugins.RemoveAll(p => p.Metadata.ID == plugin.ID); + _allLoadedPlugins.TryRemove(plugin.ID, out var _); } { _allInitializedPlugins.TryRemove(plugin.ID, out var _); From 8a2edf274a11dd0723becdf5c42097c706b5df0b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 15:36:32 +0800 Subject: [PATCH 063/125] Add AutoCompleteText property for results --- Flow.Launcher.Core/Plugin/PluginManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index b93fe3728..8b662edab 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -401,6 +401,7 @@ namespace Flow.Launcher.Core.Plugin { Title = Localize.pluginStillInitializing(metadata.Name), SubTitle = Localize.pluginStillInitializingSubtitle(), + AutoCompleteText = query.RawQuery, IcoPath = metadata.IcoPath, PluginDirectory = metadata.PluginDirectory, ActionKeywordAssigned = query.ActionKeyword, @@ -443,6 +444,7 @@ namespace Flow.Launcher.Core.Plugin { Title = Localize.pluginFailedToRespond(metadata.Name), SubTitle = Localize.pluginFailedToRespondSubtitle(), + AutoCompleteText = query.RawQuery, IcoPath = Constant.ErrorIcon, PluginDirectory = metadata.PluginDirectory, ActionKeywordAssigned = query.ActionKeyword, @@ -467,6 +469,7 @@ namespace Flow.Launcher.Core.Plugin { Title = Localize.pluginStillInitializing(metadata.Name), SubTitle = Localize.pluginStillInitializingSubtitle(), + AutoCompleteText = query.RawQuery, IcoPath = metadata.IcoPath, PluginDirectory = metadata.PluginDirectory, ActionKeywordAssigned = query.ActionKeyword, From 969b3ccbcd9ade6f7e3f1443bdf7aab7a2acda29 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 15:58:48 +0800 Subject: [PATCH 064/125] Revert CancelAsync to Cancel --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c7837ce2d..95059c150 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1331,10 +1331,7 @@ namespace Flow.Launcher.ViewModel private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - if (_updateSource != null) - { - await _updateSource.CancelAsync(); - } + _updateSource?.Cancel(); App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1913,10 +1910,7 @@ namespace Flow.Launcher.ViewModel if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - if (_dialogJumpSource != null) - { - await _dialogJumpSource.CancelAsync(); - } + _dialogJumpSource?.Cancel(); // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource(); From 54e693d0e2a53e5755b43ebdc0ecc6d31b9d750b Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 16:03:54 +0800 Subject: [PATCH 065/125] Add functions in Check Initializing & Init Failed region --- Flow.Launcher.Core/Plugin/PluginManager.cs | 37 +++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 8b662edab..dadf4b615 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -665,7 +665,7 @@ namespace Flow.Launcher.Core.Plugin #endregion - #region Check Initializing or Init Failed + #region Check Initializing & Init Failed public static bool IsInitializingOrInitFailed(string id) { @@ -685,6 +685,41 @@ namespace Flow.Launcher.Core.Plugin } } + public static bool IsInitializing(string id) + { + // Id does not exist in loaded plugins + if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; + + // Plugin initialized already + if (_allInitializedPlugins.ContainsKey(id)) + { + return false; + } + // Plugin is still initializing + else + { + return true; + } + } + + public static bool IsInitializationFailed(string id) + { + // Id does not exist in loaded plugins + if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; + + // Plugin initialized already + if (_allInitializedPlugins.ContainsKey(id)) + { + // Check if the plugin initialization failed + return _initFailedPlugins.ContainsKey(id); + } + // Plugin is still initializing + else + { + return false; + } + } + #endregion #region Plugin Action Keyword From 9d7ac851bd1c17f121da9218780d678b140c0fbd Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 16:04:26 +0800 Subject: [PATCH 066/125] Show setting panel for initializing or init failed plugins --- Flow.Launcher/ViewModel/PluginViewModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 88ebc40f9..714c63264 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -132,7 +132,6 @@ namespace Flow.Launcher.ViewModel public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public bool HasSettingControl => - !PluginManager.IsInitializingOrInitFailed(PluginPair.Metadata.ID) && // Do not show setting panel for initializing or init failed plugins PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || // Is not JsonRPC plugin jsonRPCPluginBase.NeedCreateSettingPanel()); // Is JsonRPC plugin and need to create setting panel From 793476432ba35ae347059a54cd8e15930b4e0db6 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 16:06:22 +0800 Subject: [PATCH 067/125] Show setting panel for initializing or initialization failed plugins --- Flow.Launcher/ViewModel/PluginViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 714c63264..810a238a7 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -132,6 +132,8 @@ namespace Flow.Launcher.ViewModel public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; public bool HasSettingControl => + // Here we do not check if the plugin is initialized successfully + // So we can let users change settings for initializing or initialization failed plugins PluginPair.Plugin is ISettingProvider && (PluginPair.Plugin is not JsonRPCPluginBase jsonRPCPluginBase || // Is not JsonRPC plugin jsonRPCPluginBase.NeedCreateSettingPanel()); // Is JsonRPC plugin and need to create setting panel From 6f6292494c0f37df43170cfd37e6672a14a3c210 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 3 Oct 2025 16:13:24 +0800 Subject: [PATCH 068/125] Use ContainsKey(id) for O(1) lookup instead of O(n) iteration --- Flow.Launcher.Core/Plugin/PluginManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index dadf4b615..0458ead60 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -670,7 +670,7 @@ namespace Flow.Launcher.Core.Plugin public static bool IsInitializingOrInitFailed(string id) { // Id does not exist in loaded plugins - if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; + if (!_allLoadedPlugins.ContainsKey(id)) return false; // Plugin initialized already if (_allInitializedPlugins.ContainsKey(id)) @@ -688,7 +688,7 @@ namespace Flow.Launcher.Core.Plugin public static bool IsInitializing(string id) { // Id does not exist in loaded plugins - if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; + if (!_allLoadedPlugins.ContainsKey(id)) return false; // Plugin initialized already if (_allInitializedPlugins.ContainsKey(id)) @@ -705,7 +705,7 @@ namespace Flow.Launcher.Core.Plugin public static bool IsInitializationFailed(string id) { // Id does not exist in loaded plugins - if (!_allLoadedPlugins.Any(x => x.Value.Metadata.ID == id)) return false; + if (!_allLoadedPlugins.ContainsKey(id)) return false; // Plugin initialized already if (_allInitializedPlugins.ContainsKey(id)) From 7ba4f8de4ab5c4af4ab64f638e094b0a5bb3df6d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 4 Oct 2025 02:19:57 -0300 Subject: [PATCH 069/125] feat: last opened history --- Flow.Launcher/ViewModel/MainViewModel.cs | 33 +++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2e3e8d4d7..871ac869a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -40,11 +40,11 @@ namespace Flow.Launcher.ViewModel private string _queryTextBeforeLeaveResults; private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results - private readonly FlowLauncherJsonStorage _historyItemsStorage; + private readonly FlowLauncherJsonStorage _queryHistoryItemsStorage; private readonly FlowLauncherJsonStorage _lastOpenedHistoryStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; - private readonly History _history; + private readonly History _queryHistory; private readonly LastOpenedHistory _lastOpenedHistory; private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; @@ -149,11 +149,11 @@ namespace Flow.Launcher.ViewModel } }; - _historyItemsStorage = new FlowLauncherJsonStorage(); + _queryHistoryItemsStorage = new FlowLauncherJsonStorage(); _lastOpenedHistoryStorage = new FlowLauncherJsonStorage(); _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); - _history = _historyItemsStorage.Load(); + _queryHistory = _queryHistoryItemsStorage.Load(); _lastOpenedHistory = _lastOpenedHistoryStorage.Load(); _userSelectedRecord = _userSelectedRecordStorage.Load(); @@ -356,7 +356,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _history.Items.Count - 1; + History.SelectedIndex = _queryHistory.Items.Count - 1; } else { @@ -384,10 +384,10 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - if (_history.Items.Count > 0) + if (_queryHistory.Items.Count > 0) { - ChangeQueryText(_history.Items[^lastHistoryIndex].Query); - if (lastHistoryIndex < _history.Items.Count) + ChangeQueryText(_queryHistory.Items[^lastHistoryIndex].Query); + if (lastHistoryIndex < _queryHistory.Items.Count) { lastHistoryIndex++; } @@ -397,9 +397,9 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - if (_history.Items.Count > 0) + if (_queryHistory.Items.Count > 0) { - ChangeQueryText(_history.Items[^lastHistoryIndex].Query); + ChangeQueryText(_queryHistory.Items[^lastHistoryIndex].Query); if (lastHistoryIndex > 1) { lastHistoryIndex--; @@ -535,9 +535,12 @@ namespace Flow.Launcher.ViewModel { if(Settings.ShowHistoryLastOpenedResultsForHomePage) _lastOpenedHistory.Add(result); + else + { + _queryHistory.Add(result.OriginQuery.RawQuery); + } _userSelectedRecord.Add(result); - _history.Add(result.OriginQuery.RawQuery); lastHistoryIndex = 1; } } @@ -616,7 +619,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. - && _history.Items.Count > 0) // Have history items + && _queryHistory.Items.Count > 0) // Have history items { lastHistoryIndex = 1; ReverseHistory(); @@ -1297,7 +1300,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var results = GetHistoryItems(_history.Items); + var results = GetHistoryItems(_queryHistory.Items); if (!string.IsNullOrEmpty(query)) { @@ -1571,7 +1574,7 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _history.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _queryHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = GetHistoryItems(historyItems); @@ -2208,7 +2211,7 @@ namespace Flow.Launcher.ViewModel /// public void Save() { - _historyItemsStorage.Save(); + _queryHistoryItemsStorage.Save(); _lastOpenedHistoryStorage.Save(); _userSelectedRecordStorage.Save(); _topMostRecord.Save(); From e5736567f67353de6552ece5accd5e6b9c3621db Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 7 Oct 2025 00:33:16 -0300 Subject: [PATCH 070/125] feat: created a base History model --- Flow.Launcher/Storage/History.cs | 92 +++++++++++++++++++ Flow.Launcher/Storage/HistoryItem.cs | 89 +++++++++--------- Flow.Launcher/Storage/LastOpenedHistory.cs | 42 --------- .../Storage/LastOpenedHistoryItem.cs | 17 ---- Flow.Launcher/Storage/QueryHistory.cs | 37 -------- 5 files changed, 140 insertions(+), 137 deletions(-) create mode 100644 Flow.Launcher/Storage/History.cs delete mode 100644 Flow.Launcher/Storage/LastOpenedHistory.cs delete mode 100644 Flow.Launcher/Storage/LastOpenedHistoryItem.cs delete mode 100644 Flow.Launcher/Storage/QueryHistory.cs diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs new file mode 100644 index 000000000..5a94c8a8a --- /dev/null +++ b/Flow.Launcher/Storage/History.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using JetBrains.Annotations; + +namespace Flow.Launcher.Storage +{ + public class History + { + [JsonInclude] + public List LastOpenedHistoryItems { get; private set; } = []; + [JsonInclude] + public List QueryHistoryItems { get; private set; } = []; + + private int _maxHistory = 300; + + public void AddToHistory(Result result, Settings settings) + { + if (settings.ShowHistoryQueryResultsForHomePage) + { + AddLastQuery(result); + return; + } + AddLastOpened(result); + } + + public List GetHistoryItems(Settings settings) + { + if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems; + if (settings.ShowHistoryLastOpenedResultsForHomePage) return LastOpenedHistoryItems; + return new List(); + } + + private void AddLastQuery(Result result) + { + if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; + if (QueryHistoryItems.Count > _maxHistory) + { + QueryHistoryItems.RemoveAt(0); + } + + if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().OriginQuery == result.OriginQuery) + { + QueryHistoryItems.Last().ExecutedDateTime = DateTime.Now; + } + else + { + QueryHistoryItems.Add(new HistoryItem + { + OriginQuery = result.OriginQuery, + ExecutedDateTime = DateTime.Now + }); + } + } + + private void AddLastOpened(Result result) + { + var item = new HistoryItem + { + Title = result.Title, + SubTitle = result.SubTitle, + IcoPath = result.IcoPath ?? string.Empty, + PluginID = result.PluginID, + OriginQuery = result.OriginQuery, + ExecutedDateTime = DateTime.Now, + }; + + var existing = LastOpenedHistoryItems. + FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); + + + if (existing != null) + { + existing.ExecutedDateTime = DateTime.Now; + } + else + { + if (LastOpenedHistoryItems.Count > _maxHistory) + { + LastOpenedHistoryItems.RemoveAt(0); + } + + LastOpenedHistoryItems.Add(item); + } + } + } +} diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index 6f0bf7461..cb95e75d1 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,45 +1,52 @@ -using System; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Flow.Launcher.Plugin; -namespace Flow.Launcher.Storage +namespace Flow.Launcher.Storage; +public class HistoryItem { - public class HistoryItem + public string Title { get; set; } = string.Empty; + public string SubTitle { get; set; } = string.Empty; + public string IcoPath { get; set; } = string.Empty; + public string PluginID { get; set; } = string.Empty; + public Query OriginQuery { get; set; } = null!; + public DateTime ExecutedDateTime { get; set; } + + public string GetTimeAgo() { - public string Query { get; set; } - public DateTime ExecutedDateTime { get; set; } - - public string GetTimeAgo() - { - return DateTimeAgo(ExecutedDateTime); - } - - private string DateTimeAgo(DateTime dt) - { - var span = DateTime.Now - dt; - if (span.Days > 365) - { - int years = (span.Days / 365); - if (span.Days % 365 != 0) - years += 1; - return $"about {years} {(years == 1 ? "year" : "years")} ago"; - } - if (span.Days > 30) - { - int months = (span.Days / 30); - if (span.Days % 31 != 0) - months += 1; - return $"about {months} {(months == 1 ? "month" : "months")} ago"; - } - if (span.Days > 0) - return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago"; - if (span.Hours > 0) - return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago"; - if (span.Minutes > 0) - return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago"; - if (span.Seconds > 5) - return $"about {span.Seconds} seconds ago"; - if (span.Seconds <= 5) - return "just now"; - return string.Empty; - } + return DateTimeAgo(ExecutedDateTime); } -} \ No newline at end of file + + private string DateTimeAgo(DateTime dt) + { + var span = DateTime.Now - dt; + if (span.Days > 365) + { + int years = (span.Days / 365); + if (span.Days % 365 != 0) + years += 1; + return $"about {years} {(years == 1 ? "year" : "years")} ago"; + } + if (span.Days > 30) + { + int months = (span.Days / 30); + if (span.Days % 31 != 0) + months += 1; + return $"about {months} {(months == 1 ? "month" : "months")} ago"; + } + if (span.Days > 0) + return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago"; + if (span.Hours > 0) + return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago"; + if (span.Minutes > 0) + return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago"; + if (span.Seconds > 5) + return $"about {span.Seconds} seconds ago"; + if (span.Seconds <= 5) + return "just now"; + return string.Empty; + } +} diff --git a/Flow.Launcher/Storage/LastOpenedHistory.cs b/Flow.Launcher/Storage/LastOpenedHistory.cs deleted file mode 100644 index 19f30855e..000000000 --- a/Flow.Launcher/Storage/LastOpenedHistory.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Flow.Launcher.Plugin; -using Flow.Launcher.ViewModel; - -namespace Flow.Launcher.Storage; -public class LastOpenedHistory -{ - [JsonInclude] public List Items { get; private set; } = []; - private const int MaxHistory = 300; - - public void Add(Result result) - { - var item = new LastOpenedHistoryItem - { - Title = result.Title, - SubTitle = result.SubTitle, - IcoPath = result.IcoPath ?? string.Empty, - PluginID = result.PluginID, - OriginQuery = result.OriginQuery, - ExecutedDateTime = DateTime.Now - }; - - // Aparenta estar duplicando... - var existing = Items.FirstOrDefault(x => x.OriginQuery.RawQuery == item.OriginQuery.RawQuery && x.PluginID == item.PluginID); - if (existing != null) - { - Items.Remove(existing); - } - - Items.Add(item); - - if (Items.Count > MaxHistory) - { - Items.RemoveAt(0); - } - } -} diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs deleted file mode 100644 index 28bd49a95..000000000 --- a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Flow.Launcher.Plugin; - -namespace Flow.Launcher.Storage; -public class LastOpenedHistoryItem -{ - public string Title { get; set; } = string.Empty; - public string SubTitle { get; set; } = string.Empty; - public string IcoPath { get; set; } = string.Empty; - public string PluginID { get; set; } = string.Empty; - public Query OriginQuery { get; set; } = null!; - public DateTime ExecutedDateTime { get; set; } -} diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs deleted file mode 100644 index a5383b179..000000000 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Serialization; - -namespace Flow.Launcher.Storage -{ - public class History - { - [JsonInclude] - public List Items { get; private set; } = new List(); - - private int _maxHistory = 300; - - public void Add(string query) - { - if (string.IsNullOrEmpty(query)) return; - if (Items.Count > _maxHistory) - { - Items.RemoveAt(0); - } - - if (Items.Count > 0 && Items.Last().Query == query) - { - Items.Last().ExecutedDateTime = DateTime.Now; - } - else - { - Items.Add(new HistoryItem - { - Query = query, - ExecutedDateTime = DateTime.Now - }); - } - } - } -} From 9e182a2e47442f187b8556ec7ff85c12e3500739 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 7 Oct 2025 00:35:34 -0300 Subject: [PATCH 071/125] feat: base history model in MainView and refactoring code to replace action between query result or last opened --- Flow.Launcher/ViewModel/MainViewModel.cs | 154 ++++++++++------------- 1 file changed, 69 insertions(+), 85 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 871ac869a..d4dda62aa 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -8,21 +8,23 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Windows; -using System.Windows.Input; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; +using Droplex; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.DialogJump; +using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Storage; +using JetBrains.Annotations; using Microsoft.VisualStudio.Threading; using ModernWpf; @@ -39,13 +41,10 @@ namespace Flow.Launcher.ViewModel private bool _previousIsHomeQuery; private string _queryTextBeforeLeaveResults; private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results - - private readonly FlowLauncherJsonStorage _queryHistoryItemsStorage; - private readonly FlowLauncherJsonStorage _lastOpenedHistoryStorage; + private readonly FlowLauncherJsonStorage _historyStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; - private readonly History _queryHistory; - private readonly LastOpenedHistory _lastOpenedHistory; + private readonly History _history; private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; @@ -149,12 +148,13 @@ namespace Flow.Launcher.ViewModel } }; - _queryHistoryItemsStorage = new FlowLauncherJsonStorage(); - _lastOpenedHistoryStorage = new FlowLauncherJsonStorage(); + _historyStorage = new FlowLauncherJsonStorage(); + _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); - _queryHistory = _queryHistoryItemsStorage.Load(); - _lastOpenedHistory = _lastOpenedHistoryStorage.Load(); + + + _history = _historyStorage.Load(); _userSelectedRecord = _userSelectedRecordStorage.Load(); ContextMenu = new ResultsViewModel(Settings, this) @@ -356,7 +356,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _queryHistory.Items.Count - 1; + History.SelectedIndex = _history.GetHistoryItems(Settings).Count - 1; } else { @@ -384,10 +384,11 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - if (_queryHistory.Items.Count > 0) + var historyItems = _history.GetHistoryItems(Settings); + if (historyItems.Count > 0) { - ChangeQueryText(_queryHistory.Items[^lastHistoryIndex].Query); - if (lastHistoryIndex < _queryHistory.Items.Count) + ChangeQueryText(historyItems[^lastHistoryIndex].OriginQuery.RawQuery); + if (lastHistoryIndex < historyItems.Count) { lastHistoryIndex++; } @@ -397,9 +398,11 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - if (_queryHistory.Items.Count > 0) + var historyItems = _history.GetHistoryItems(Settings); + + if (historyItems.Count > 0) { - ChangeQueryText(_queryHistory.Items[^lastHistoryIndex].Query); + ChangeQueryText(historyItems[^lastHistoryIndex].OriginQuery.RawQuery); if (lastHistoryIndex > 1) { lastHistoryIndex--; @@ -533,13 +536,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { - if(Settings.ShowHistoryLastOpenedResultsForHomePage) - _lastOpenedHistory.Add(result); - else - { - _queryHistory.Add(result.OriginQuery.RawQuery); - } - + _history.AddToHistory(result, Settings); _userSelectedRecord.Add(result); lastHistoryIndex = 1; } @@ -616,10 +613,11 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { + var historyItems = _history.GetHistoryItems(Settings); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. - && _queryHistory.Items.Count > 0) // Have history items + && historyItems.Count > 0) // Have history items { lastHistoryIndex = 1; ReverseHistory(); @@ -1300,7 +1298,9 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var results = GetHistoryItems(_queryHistory.Items); + var items = _history.GetHistoryItems(Settings); + + var results = GetHistoryResults(items); if (!string.IsNullOrEmpty(query)) { @@ -1317,32 +1317,60 @@ namespace Flow.Launcher.ViewModel } } - private static List GetHistoryItems(IEnumerable historyItems) + private List GetHistoryResults(IEnumerable historyItems) { var results = new List(); + Func defaultAction = _ => false; + foreach (var h in historyItems) { - var title = App.API.GetTranslation("executeQuery"); - var time = App.API.GetTranslation("lastExecuteTime"); - var result = new Result + // Achar uma forma melhor de fazer isso + if (Settings.ShowHistoryLastOpenedResultsForHomePage) { - Title = string.Format(title, h.Query), - SubTitle = string.Format(time, h.ExecutedDateTime), - IcoPath = Constant.HistoryIcon, - OriginQuery = new Query { RawQuery = h.Query }, - Action = _ => + var pluginPair = PluginManager.GetPluginForId(h.PluginID); + if (pluginPair != null) + { + var queryResults = PluginManager + .QueryForPluginAsync(pluginPair, h.OriginQuery, CancellationToken.None) + .GetAwaiter() + .GetResult(); + var originalResult = queryResults?.FirstOrDefault(r => + r.Title == h.Title && r.SubTitle == h.SubTitle); + if (originalResult?.Action != null) + { + defaultAction = originalResult.Action; + } + + } + } + else + { + defaultAction = _ => { App.API.BackToQueryResults(); - App.API.ChangeQuery(h.Query); + App.API.ChangeQuery(h.OriginQuery.RawQuery); return false; - }, + }; + } + var timeT = App.API.GetTranslation("lastExecuteTime"); + var result = new Result + { + Title = Settings.ShowHistoryQueryResultsForHomePage ? string.Format(App.API.GetTranslation("executeQuery"), h.OriginQuery.RawQuery) : h.Title, + SubTitle = string.Format(timeT, h.ExecutedDateTime), + IcoPath = Constant.HistoryIcon, + PluginID = h.PluginID, + OriginQuery = h.OriginQuery, + Action = defaultAction, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; results.Add(result); } return results; + } + + private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); @@ -1457,15 +1485,7 @@ namespace Flow.Launcher.ViewModel true => Task.CompletedTask }).ToArray(); - // Query history results for home page firstly so it will be put on top of the results - if (Settings.ShowHistoryQueryResultsForHomePage) - { - QueryHistoryTask(currentCancellationToken); - } - else if (Settings.ShowHistoryLastOpenedResultsForHomePage) - { - QueryLastOpenedHistoryTask(currentCancellationToken); - } + QueryHistoryTask(currentCancellationToken); } else { @@ -1574,9 +1594,9 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _queryHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _history.GetHistoryItems(Settings).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); - var results = GetHistoryItems(historyItems); + var results = GetHistoryResults(historyItems); if (token.IsCancellationRequested) return; @@ -1588,41 +1608,6 @@ namespace Flow.Launcher.ViewModel App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } } - - void QueryLastOpenedHistoryTask(CancellationToken token) - { - var historyItems = _lastOpenedHistory.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); - - var results = new List(); - foreach (var item in historyItems) - { - var result = new Result - { - Title = item.Title, - SubTitle = item.SubTitle, - IcoPath = item.IcoPath, - PluginID = item.PluginID, - OriginQuery = item.OriginQuery, - Action = _ => - { - App.API.ChangeQuery(item.OriginQuery.RawQuery); - return false; - }, - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") - }; - results.Add(result); - } - - if (token.IsCancellationRequested) return; - - App.API.LogDebug(ClassName, "Update results for last opened history"); - - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, - token, reSelect))) - { - App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); - } - } } private async Task ConstructQueryAsync(string queryText, IEnumerable customShortcuts, @@ -2211,8 +2196,7 @@ namespace Flow.Launcher.ViewModel /// public void Save() { - _queryHistoryItemsStorage.Save(); - _lastOpenedHistoryStorage.Save(); + _historyStorage.Save(); _userSelectedRecordStorage.Save(); _topMostRecord.Save(); } From 368134058730fe0f79aaef9fcb47d3179a63342f Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 21:11:27 -0300 Subject: [PATCH 072/125] feat: toggle history --- .../Views/SettingsPaneGeneral.xaml | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index e694a84d8..e722e9734 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -374,27 +374,48 @@ - - - - - - - - - - + + + + + + + + Date: Wed, 8 Oct 2025 21:12:14 -0300 Subject: [PATCH 073/125] faet: query action save --- Flow.Launcher/Storage/History.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 5a94c8a8a..33128256e 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -20,7 +20,7 @@ namespace Flow.Launcher.Storage private int _maxHistory = 300; public void AddToHistory(Result result, Settings settings) - { + { if (settings.ShowHistoryQueryResultsForHomePage) { AddLastQuery(result); @@ -44,7 +44,7 @@ namespace Flow.Launcher.Storage QueryHistoryItems.RemoveAt(0); } - if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().OriginQuery == result.OriginQuery) + if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().OriginQuery.RawQuery == result.OriginQuery.RawQuery) { QueryHistoryItems.Last().ExecutedDateTime = DateTime.Now; } @@ -53,7 +53,13 @@ namespace Flow.Launcher.Storage QueryHistoryItems.Add(new HistoryItem { OriginQuery = result.OriginQuery, - ExecutedDateTime = DateTime.Now + ExecutedDateTime = DateTime.Now, + QueryAction = _ => + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(result.OriginQuery.RawQuery); + return false; + } }); } } @@ -68,6 +74,7 @@ namespace Flow.Launcher.Storage PluginID = result.PluginID, OriginQuery = result.OriginQuery, ExecutedDateTime = DateTime.Now, + ExecuteAction = result.Action }; var existing = LastOpenedHistoryItems. From 06711d3b8b55b7d3d8bca37e99e4d0a7c1fb8f6b Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 21:12:36 -0300 Subject: [PATCH 074/125] feat: Saving actions for history --- Flow.Launcher/Storage/HistoryItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index cb95e75d1..e8838d169 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -14,6 +14,9 @@ public class HistoryItem public string PluginID { get; set; } = string.Empty; public Query OriginQuery { get; set; } = null!; public DateTime ExecutedDateTime { get; set; } + public Func ExecuteAction { get; set; } + public Func QueryAction { get; set; } + public string GetTimeAgo() { From c051c5cd50b8c997f59818f5b809040b980ea2c4 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 21:15:10 -0300 Subject: [PATCH 075/125] feat: new history logic in MainViewModel --- .../UserSettings/Settings.cs | 22 +++++++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 37 +++---------------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index e33bf04ac..0c839c497 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -215,6 +215,28 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } } + private bool _showHistoryOnHomePage = true; + public bool ShowHistoryOnHomePage + { + get + { + if (ShowHistoryQueryResultsForHomePage || ShowHistoryLastOpenedResultsForHomePage) return true; + return _showHistoryOnHomePage; + } + set + { + if (_showHistoryOnHomePage != value) + { + _showHistoryOnHomePage = value; + OnPropertyChanged(); + if (value == false) + { + ShowHistoryQueryResultsForHomePage = false; + ShowHistoryLastOpenedResultsForHomePage = false; + } + } + } + } private bool _showHistoryQueryResultsForHomePage = false; public bool ShowHistoryQueryResultsForHomePage diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d4dda62aa..23dac59e9 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -536,7 +536,10 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { - _history.AddToHistory(result, Settings); + if (Settings.ShowHistoryOnHomePage) + { + _history.AddToHistory(result, Settings); + } _userSelectedRecord.Add(result); lastHistoryIndex = 1; } @@ -1320,38 +1323,8 @@ namespace Flow.Launcher.ViewModel private List GetHistoryResults(IEnumerable historyItems) { var results = new List(); - Func defaultAction = _ => false; - foreach (var h in historyItems) { - // Achar uma forma melhor de fazer isso - if (Settings.ShowHistoryLastOpenedResultsForHomePage) - { - var pluginPair = PluginManager.GetPluginForId(h.PluginID); - if (pluginPair != null) - { - var queryResults = PluginManager - .QueryForPluginAsync(pluginPair, h.OriginQuery, CancellationToken.None) - .GetAwaiter() - .GetResult(); - var originalResult = queryResults?.FirstOrDefault(r => - r.Title == h.Title && r.SubTitle == h.SubTitle); - if (originalResult?.Action != null) - { - defaultAction = originalResult.Action; - } - - } - } - else - { - defaultAction = _ => - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(h.OriginQuery.RawQuery); - return false; - }; - } var timeT = App.API.GetTranslation("lastExecuteTime"); var result = new Result { @@ -1360,7 +1333,7 @@ namespace Flow.Launcher.ViewModel IcoPath = Constant.HistoryIcon, PluginID = h.PluginID, OriginQuery = h.OriginQuery, - Action = defaultAction, + Action = Settings.ShowHistoryLastOpenedResultsForHomePage ? h.ExecuteAction : h.QueryAction, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; results.Add(result); From b290055e83cedecd59019cd03e8b843fc255e243 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 21:25:13 -0300 Subject: [PATCH 076/125] feat: up --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 23dac59e9..e119ebb26 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1458,7 +1458,10 @@ namespace Flow.Launcher.ViewModel true => Task.CompletedTask }).ToArray(); - QueryHistoryTask(currentCancellationToken); + if (Settings.ShowHistoryOnHomePage) + { + QueryHistoryTask(currentCancellationToken); + } } else { From 156cb3055c900beff507aee0db7559079a5fa1a1 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 22:06:39 -0300 Subject: [PATCH 077/125] merge --- .../Views/SettingsPaneGeneral.xaml | 70 ++++++--------- Flow.Launcher/ViewModel/MainViewModel.cs | 86 +++++++++++-------- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index bf7aaecf2..ddd7747d1 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -392,55 +392,37 @@ OnContent="{DynamicResource enable}" /> - + - - - - - - - - - - + + + + + + + + + + + + _historyStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; @@ -241,14 +244,16 @@ namespace Flow.Launcher.ViewModel // Indicate if to clear existing results so to show only ones from plugins with action keywords var query = item.Query; var currentIsHomeQuery = query.IsHomeQuery; - var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); + var shouldClearExistingResults = + ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); _lastQuery = item.Query; _previousIsHomeQuery = currentIsHomeQuery; // If the queue already has the item, we need to pass the shouldClearExistingResults flag if (queue.TryGetValue(item.ID, out var existingItem)) { - item.ShouldClearExistingResults = shouldClearExistingResults || existingItem.ShouldClearExistingResults; + item.ShouldClearExistingResults = shouldClearExistingResults || + existingItem.ShouldClearExistingResults; } else { @@ -319,7 +324,7 @@ namespace Flow.Launcher.ViewModel App.API.LogDebug(ClassName, $"Update results for plugin <{pair.Metadata.Name}>"); if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, - token))) + token))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } @@ -422,9 +427,11 @@ namespace Flow.Launcher.ViewModel if (result is DialogJumpResult dialogJumpResult) { Win32Helper.SetForegroundWindow(DialogWindowHandle); - _ = Task.Run(() => DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath)); + _ = Task.Run(() => + DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath)); } } + return; } @@ -540,12 +547,14 @@ namespace Flow.Launcher.ViewModel { _history.AddToHistory(result, Settings); } + _userSelectedRecord.Add(result); lastHistoryIndex = 1; } } - private static IReadOnlyList DeepCloneResults(IReadOnlyList results, bool isDialogJump, CancellationToken token = default) + private static IReadOnlyList DeepCloneResults(IReadOnlyList results, bool isDialogJump, + CancellationToken token = default) { var resultsCopy = new List(); @@ -569,7 +578,7 @@ namespace Flow.Launcher.ViewModel resultsCopy.Add(resultCopy); } } - + return resultsCopy; } @@ -619,7 +628,8 @@ namespace Flow.Launcher.ViewModel var historyItems = _history.GetHistoryItems(Settings); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input - && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. + && Results.Visibility != + Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. && historyItems.Count > 0) // Have history items { lastHistoryIndex = 1; @@ -692,6 +702,7 @@ namespace Flow.Launcher.ViewModel public bool GameModeStatus { get; set; } = false; private string _queryText; + public string QueryText { get => _queryText; @@ -853,7 +864,8 @@ namespace Flow.Launcher.ViewModel // If we are returning from history and we have not set select item yet, // we need to clear the preview selected item - if (isReturningFromHistory && _selectedItemFromQueryResults.HasValue && (!_selectedItemFromQueryResults.Value)) + if (isReturningFromHistory && _selectedItemFromQueryResults.HasValue && + (!_selectedItemFromQueryResults.Value)) { PreviewSelectedItem = null; } @@ -871,6 +883,7 @@ namespace Flow.Launcher.ViewModel ContextMenu.Visibility = Visibility.Visible; History.Visibility = Visibility.Collapsed; } + _queryTextBeforeLeaveResults = QueryText; // Because of Fody's optimization @@ -886,7 +899,8 @@ namespace Flow.Launcher.ViewModel { // If we are returning from query results and we have not set select item yet, // we need to clear the preview selected item - if (isReturningFromQueryResults && _selectedItemFromQueryResults.HasValue && _selectedItemFromQueryResults.Value) + if (isReturningFromQueryResults && _selectedItemFromQueryResults.HasValue && + _selectedItemFromQueryResults.Value) { PreviewSelectedItem = null; } @@ -896,7 +910,9 @@ namespace Flow.Launcher.ViewModel } public Visibility ShowCustomizedPreview - => InternalPreviewVisible && PreviewSelectedItem?.Result.PreviewPanel != null ? Visibility.Visible : Visibility.Collapsed; + => InternalPreviewVisible && PreviewSelectedItem?.Result.PreviewPanel != null + ? Visibility.Visible + : Visibility.Collapsed; public UserControl CustomizedPreviewControl => ShowCustomizedPreview == Visibility.Visible ? PreviewSelectedItem?.Result.PreviewPanel.Value : null; @@ -917,9 +933,10 @@ namespace Flow.Launcher.ViewModel public double SearchIconOpacity { get; set; } = 1; private string _placeholderText; + public string PlaceholderText { - get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText; + get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder() : _placeholderText; set { _placeholderText = value; @@ -1015,6 +1032,7 @@ namespace Flow.Launcher.ViewModel private bool? _selectedItemFromQueryResults; private ResultViewModel _previewSelectedItem; + public ResultViewModel PreviewSelectedItem { get => _previewSelectedItem; @@ -1035,7 +1053,8 @@ namespace Flow.Launcher.ViewModel if (ResultAreaColumn == ResultAreaColumnPreviewHidden) return false; #if DEBUG - throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); + throw new NotImplementedException( + "ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); #else App.API.LogError(ClassName, "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible"); return false; @@ -1161,6 +1180,7 @@ namespace Flow.Launcher.ViewModel HideInternalPreview(); _ = OpenExternalPreviewAsync(path); } + break; case true when !CanExternalPreviewSelectedResult(out var _): @@ -1169,6 +1189,7 @@ namespace Flow.Launcher.ViewModel await CloseExternalPreviewAsync(); ShowInternalPreview(); } + break; case false when InternalPreviewVisible: @@ -1257,10 +1278,7 @@ namespace Flow.Launcher.ViewModel List results; if (selected.PluginID == null) // SelectedItem from history in home page. { - results = new() - { - ContextMenuTopMost(selected) - }; + results = new() { ContextMenuTopMost(selected) }; } else { @@ -1272,20 +1290,19 @@ namespace Flow.Launcher.ViewModel if (!string.IsNullOrEmpty(query)) { var filtered = results.Select(x => x.Clone()).Where - ( - r => + (r => + { + var match = App.API.FuzzySearch(query, r.Title); + if (!match.IsSearchPrecisionScoreMet()) { - var match = App.API.FuzzySearch(query, r.Title); - if (!match.IsSearchPrecisionScoreMet()) - { - match = App.API.FuzzySearch(query, r.SubTitle); - } + match = App.API.FuzzySearch(query, r.SubTitle); + } - if (!match.IsSearchPrecisionScoreMet()) return false; + if (!match.IsSearchPrecisionScoreMet()) return false; - r.Score = match.Score; - return true; - }).ToList(); + r.Score = match.Score; + return true; + }).ToList(); ContextMenu.AddResults(filtered, id); } else @@ -1308,9 +1325,8 @@ namespace Flow.Launcher.ViewModel if (!string.IsNullOrEmpty(query)) { var filtered = results.Where - ( - r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || - App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + (r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() ).ToList(); History.AddResults(filtered, id); } @@ -1320,14 +1336,15 @@ namespace Flow.Launcher.ViewModel } } - private List GetHistoryResults(IEnumerable historyItems) + private List GetHistoryResults(IEnumerable historyItems) { var results = new List(); foreach (var h in historyItems) { var result = new Result { - Title = Settings.ShowHistoryLastOpenedResultsForHomePage ? Localize.executeQuery(h.Query) : h.Title + Title = + Settings.ShowHistoryQueryResultsForHomePage ? Localize.executeQuery(h.OriginQuery.RawQuery) : h.Title, SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), IcoPath = Constant.HistoryIcon, PluginID = h.PluginID, @@ -1337,10 +1354,11 @@ namespace Flow.Launcher.ViewModel }; results.Add(result); } + return results; } - private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) + private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); From d122276e7193060e1eed9b4ccb7e766405033127 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 8 Oct 2025 22:09:09 -0300 Subject: [PATCH 078/125] feat: clean imports --- Flow.Launcher/Storage/HistoryItem.cs | 39 ---------------------------- 1 file changed, 39 deletions(-) diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index e8838d169..d7503108c 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; @@ -17,39 +13,4 @@ public class HistoryItem public Func ExecuteAction { get; set; } public Func QueryAction { get; set; } - - public string GetTimeAgo() - { - return DateTimeAgo(ExecutedDateTime); - } - - private string DateTimeAgo(DateTime dt) - { - var span = DateTime.Now - dt; - if (span.Days > 365) - { - int years = (span.Days / 365); - if (span.Days % 365 != 0) - years += 1; - return $"about {years} {(years == 1 ? "year" : "years")} ago"; - } - if (span.Days > 30) - { - int months = (span.Days / 30); - if (span.Days % 31 != 0) - months += 1; - return $"about {months} {(months == 1 ? "month" : "months")} ago"; - } - if (span.Days > 0) - return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago"; - if (span.Hours > 0) - return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago"; - if (span.Minutes > 0) - return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago"; - if (span.Seconds > 5) - return $"about {span.Seconds} seconds ago"; - if (span.Seconds <= 5) - return "just now"; - return string.Empty; - } } From 50f5e850dd4e45d698dd7e891c4de3f0fbe5961d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Thu, 9 Oct 2025 23:47:59 -0300 Subject: [PATCH 079/125] feat: code quality --- Flow.Launcher/Storage/History.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 33128256e..9155f02d8 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -32,8 +32,7 @@ namespace Flow.Launcher.Storage public List GetHistoryItems(Settings settings) { if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems; - if (settings.ShowHistoryLastOpenedResultsForHomePage) return LastOpenedHistoryItems; - return new List(); + return LastOpenedHistoryItems; } private void AddLastQuery(Result result) From 545c4208d95aaef87f4bed87092739e9bd09020d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Fri, 10 Oct 2025 00:07:24 -0300 Subject: [PATCH 080/125] code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 72 ++++++++++-------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ef2aea868..7512ae4c2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -40,9 +40,7 @@ namespace Flow.Launcher.ViewModel private Query _lastQuery; private bool _previousIsHomeQuery; private string _queryTextBeforeLeaveResults; - - private string - _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results + private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results private readonly FlowLauncherJsonStorage _historyStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; @@ -156,7 +154,6 @@ namespace Flow.Launcher.ViewModel _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); - _history = _historyStorage.Load(); _userSelectedRecord = _userSelectedRecordStorage.Load(); @@ -244,16 +241,14 @@ namespace Flow.Launcher.ViewModel // Indicate if to clear existing results so to show only ones from plugins with action keywords var query = item.Query; var currentIsHomeQuery = query.IsHomeQuery; - var shouldClearExistingResults = - ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); + var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); _lastQuery = item.Query; _previousIsHomeQuery = currentIsHomeQuery; // If the queue already has the item, we need to pass the shouldClearExistingResults flag if (queue.TryGetValue(item.ID, out var existingItem)) { - item.ShouldClearExistingResults = shouldClearExistingResults || - existingItem.ShouldClearExistingResults; + item.ShouldClearExistingResults = shouldClearExistingResults || existingItem.ShouldClearExistingResults; } else { @@ -324,7 +319,7 @@ namespace Flow.Launcher.ViewModel App.API.LogDebug(ClassName, $"Update results for plugin <{pair.Metadata.Name}>"); if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, - token))) + token))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } @@ -427,8 +422,7 @@ namespace Flow.Launcher.ViewModel if (result is DialogJumpResult dialogJumpResult) { Win32Helper.SetForegroundWindow(DialogWindowHandle); - _ = Task.Run(() => - DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath)); + _ = Task.Run(() => DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath)); } } @@ -553,8 +547,7 @@ namespace Flow.Launcher.ViewModel } } - private static IReadOnlyList DeepCloneResults(IReadOnlyList results, bool isDialogJump, - CancellationToken token = default) + private static IReadOnlyList DeepCloneResults(IReadOnlyList results, bool isDialogJump, CancellationToken token = default) { var resultsCopy = new List(); @@ -578,7 +571,6 @@ namespace Flow.Launcher.ViewModel resultsCopy.Add(resultCopy); } } - return resultsCopy; } @@ -628,9 +620,8 @@ namespace Flow.Launcher.ViewModel var historyItems = _history.GetHistoryItems(Settings); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input - && Results.Visibility != - Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. - && historyItems.Count > 0) // Have history items + && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. + && historyItems.Count > 0) { lastHistoryIndex = 1; ReverseHistory(); @@ -702,7 +693,6 @@ namespace Flow.Launcher.ViewModel public bool GameModeStatus { get; set; } = false; private string _queryText; - public string QueryText { get => _queryText; @@ -864,8 +854,7 @@ namespace Flow.Launcher.ViewModel // If we are returning from history and we have not set select item yet, // we need to clear the preview selected item - if (isReturningFromHistory && _selectedItemFromQueryResults.HasValue && - (!_selectedItemFromQueryResults.Value)) + if (isReturningFromHistory && _selectedItemFromQueryResults.HasValue && (!_selectedItemFromQueryResults.Value)) { PreviewSelectedItem = null; } @@ -883,7 +872,6 @@ namespace Flow.Launcher.ViewModel ContextMenu.Visibility = Visibility.Visible; History.Visibility = Visibility.Collapsed; } - _queryTextBeforeLeaveResults = QueryText; // Because of Fody's optimization @@ -899,8 +887,7 @@ namespace Flow.Launcher.ViewModel { // If we are returning from query results and we have not set select item yet, // we need to clear the preview selected item - if (isReturningFromQueryResults && _selectedItemFromQueryResults.HasValue && - _selectedItemFromQueryResults.Value) + if (isReturningFromQueryResults && _selectedItemFromQueryResults.HasValue && _selectedItemFromQueryResults.Value) { PreviewSelectedItem = null; } @@ -910,9 +897,7 @@ namespace Flow.Launcher.ViewModel } public Visibility ShowCustomizedPreview - => InternalPreviewVisible && PreviewSelectedItem?.Result.PreviewPanel != null - ? Visibility.Visible - : Visibility.Collapsed; + => InternalPreviewVisible && PreviewSelectedItem?.Result.PreviewPanel != null ? Visibility.Visible : Visibility.Collapsed; public UserControl CustomizedPreviewControl => ShowCustomizedPreview == Visibility.Visible ? PreviewSelectedItem?.Result.PreviewPanel.Value : null; @@ -933,7 +918,6 @@ namespace Flow.Launcher.ViewModel public double SearchIconOpacity { get; set; } = 1; private string _placeholderText; - public string PlaceholderText { get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder() : _placeholderText; @@ -1032,7 +1016,6 @@ namespace Flow.Launcher.ViewModel private bool? _selectedItemFromQueryResults; private ResultViewModel _previewSelectedItem; - public ResultViewModel PreviewSelectedItem { get => _previewSelectedItem; @@ -1053,8 +1036,7 @@ namespace Flow.Launcher.ViewModel if (ResultAreaColumn == ResultAreaColumnPreviewHidden) return false; #if DEBUG - throw new NotImplementedException( - "ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); + throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); #else App.API.LogError(ClassName, "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible"); return false; @@ -1189,7 +1171,6 @@ namespace Flow.Launcher.ViewModel await CloseExternalPreviewAsync(); ShowInternalPreview(); } - break; case false when InternalPreviewVisible: @@ -1278,7 +1259,10 @@ namespace Flow.Launcher.ViewModel List results; if (selected.PluginID == null) // SelectedItem from history in home page. { - results = new() { ContextMenuTopMost(selected) }; + results = new() + { + ContextMenuTopMost(selected) + }; } else { @@ -1290,15 +1274,16 @@ namespace Flow.Launcher.ViewModel if (!string.IsNullOrEmpty(query)) { var filtered = results.Select(x => x.Clone()).Where - (r => - { - var match = App.API.FuzzySearch(query, r.Title); - if (!match.IsSearchPrecisionScoreMet()) - { - match = App.API.FuzzySearch(query, r.SubTitle); - } + ( + r => + { + var match = App.API.FuzzySearch(query, r.Title); + if (!match.IsSearchPrecisionScoreMet()) + { + match = App.API.FuzzySearch(query, r.SubTitle); + } - if (!match.IsSearchPrecisionScoreMet()) return false; + if (!match.IsSearchPrecisionScoreMet()) return false; r.Score = match.Score; return true; @@ -1325,9 +1310,10 @@ namespace Flow.Launcher.ViewModel if (!string.IsNullOrEmpty(query)) { var filtered = results.Where - (r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || - App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() - ).ToList(); + ( + r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + ).ToList(); History.AddResults(filtered, id); } else From 8e8e9d35ac0eef9760c29a2ab3660573cffb6b87 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Fri, 10 Oct 2025 00:08:27 -0300 Subject: [PATCH 081/125] code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 7512ae4c2..ad530cc1d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -10,12 +10,10 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Input; using System.Windows.Controls; -using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; -using Droplex; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; From 9e1b8c1a729fddef291332bc30443d1cb512b93c Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 02:43:27 -0300 Subject: [PATCH 082/125] feat: Populate new history system with legacy query history --- Flow.Launcher/Storage/History.cs | 96 ++++++++++++++++------ Flow.Launcher/Storage/HistoryItem.cs | 6 +- Flow.Launcher/Storage/HistoryItemLegacy.cs | 13 +++ Flow.Launcher/Storage/HistoryLegacy.cs | 12 +++ Flow.Launcher/ViewModel/MainViewModel.cs | 24 +++--- 5 files changed, 114 insertions(+), 37 deletions(-) create mode 100644 Flow.Launcher/Storage/HistoryItemLegacy.cs create mode 100644 Flow.Launcher/Storage/HistoryLegacy.cs diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 9155f02d8..0dd192558 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -1,38 +1,89 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Flow.Launcher.Infrastructure.UserSettings; +using System.Threading; +using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; -using JetBrains.Annotations; namespace Flow.Launcher.Storage { + public class History { - [JsonInclude] - public List LastOpenedHistoryItems { get; private set; } = []; - [JsonInclude] - public List QueryHistoryItems { get; private set; } = []; + //Legacy + [JsonInclude] public List Items { get; private set; } = []; + [JsonInclude] public List LastOpenedHistoryItems { get; private set; } = []; + [JsonInclude] public List QueryHistoryItems { get; private set; } = []; private int _maxHistory = 300; - public void AddToHistory(Result result, Settings settings) - { - if (settings.ShowHistoryQueryResultsForHomePage) + public void AddToHistory(Result result, bool isQuery) + { + if (isQuery) { - AddLastQuery(result); + AddLastQuery(result); return; } + AddLastOpened(result); } - public List GetHistoryItems(Settings settings) - { - if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems; - return LastOpenedHistoryItems; + + + public List GetHistoryItems(bool isQuery) + { + if (isQuery) return PopulateActions(QueryHistoryItems, isQuery); + return PopulateActions(LastOpenedHistoryItems, isQuery); + } + + public void PopulateHistoryWithLegacyHistory() + { + foreach (var item in Items) + { + QueryHistoryItems.Add(new HistoryItem + { + RawQuery = item.Query, + ExecutedDateTime = item.ExecutedDateTime ?? DateTime.Now, + QueryAction = GetQueryAction(item.Query) + }); + } + if (Items.Any()) Items.Clear(); + } + + private List PopulateActions(List items,bool isQuery) + { + + foreach (var item in items) + { + if (item.QueryAction != null && item.ExecuteAction != null) continue; + if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.RawQuery); + if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle); + } + + return items; + } + + private Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) + { + var plugin = PluginManager.GetPluginForId(pluginId); + + var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); + var freshResults = plugin.Plugin + .QueryAsync(query, CancellationToken.None) + .GetAwaiter() + .GetResult(); + return freshResults?.FirstOrDefault(r => r.Title == title + && r.SubTitle == subTitle)?.Action; + } + private Func GetQueryAction(string query) + { + return _=> + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(query); + return false; + }; } private void AddLastQuery(Result result) @@ -43,7 +94,7 @@ namespace Flow.Launcher.Storage QueryHistoryItems.RemoveAt(0); } - if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().OriginQuery.RawQuery == result.OriginQuery.RawQuery) + if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().RawQuery == result.OriginQuery.RawQuery) { QueryHistoryItems.Last().ExecutedDateTime = DateTime.Now; } @@ -51,14 +102,9 @@ namespace Flow.Launcher.Storage { QueryHistoryItems.Add(new HistoryItem { - OriginQuery = result.OriginQuery, + RawQuery = result.OriginQuery.RawQuery, ExecutedDateTime = DateTime.Now, - QueryAction = _ => - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(result.OriginQuery.RawQuery); - return false; - } + QueryAction = GetQueryAction(result.OriginQuery.RawQuery) }); } } @@ -71,7 +117,7 @@ namespace Flow.Launcher.Storage SubTitle = result.SubTitle, IcoPath = result.IcoPath ?? string.Empty, PluginID = result.PluginID, - OriginQuery = result.OriginQuery, + RawQuery = result.OriginQuery.RawQuery, ExecutedDateTime = DateTime.Now, ExecuteAction = result.Action }; diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index d7503108c..fb63aeffc 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; @@ -8,9 +9,12 @@ public class HistoryItem public string SubTitle { get; set; } = string.Empty; public string IcoPath { get; set; } = string.Empty; public string PluginID { get; set; } = string.Empty; - public Query OriginQuery { get; set; } = null!; + public string RawQuery { get; set; } + public DateTime ExecutedDateTime { get; set; } + [JsonIgnore] public Func ExecuteAction { get; set; } + [JsonIgnore] public Func QueryAction { get; set; } } diff --git a/Flow.Launcher/Storage/HistoryItemLegacy.cs b/Flow.Launcher/Storage/HistoryItemLegacy.cs new file mode 100644 index 000000000..1ae62ff9e --- /dev/null +++ b/Flow.Launcher/Storage/HistoryItemLegacy.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Flow.Launcher.Storage; +public class HistoryItemLegacy +{ + public string Query { get; set; } + public DateTime? ExecutedDateTime { get; set; } + +} diff --git a/Flow.Launcher/Storage/HistoryLegacy.cs b/Flow.Launcher/Storage/HistoryLegacy.cs new file mode 100644 index 000000000..8311c0a8d --- /dev/null +++ b/Flow.Launcher/Storage/HistoryLegacy.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Flow.Launcher.Storage; +public class HistoryLegacy +{ + [JsonInclude] public List Items { get; private set; } = []; +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ad530cc1d..db67539aa 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -153,6 +153,7 @@ namespace Flow.Launcher.ViewModel _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); _history = _historyStorage.Load(); + _history.PopulateHistoryWithLegacyHistory(); _userSelectedRecord = _userSelectedRecordStorage.Load(); ContextMenu = new ResultsViewModel(Settings, this) @@ -354,7 +355,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _history.GetHistoryItems(Settings).Count - 1; + History.SelectedIndex = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage).Count - 1; } else { @@ -382,10 +383,10 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - var historyItems = _history.GetHistoryItems(Settings); + var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); if (historyItems.Count > 0) { - ChangeQueryText(historyItems[^lastHistoryIndex].OriginQuery.RawQuery); + ChangeQueryText(historyItems[^lastHistoryIndex].RawQuery); if (lastHistoryIndex < historyItems.Count) { lastHistoryIndex++; @@ -396,11 +397,11 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - var historyItems = _history.GetHistoryItems(Settings); + var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); if (historyItems.Count > 0) { - ChangeQueryText(historyItems[^lastHistoryIndex].OriginQuery.RawQuery); + ChangeQueryText(historyItems[^lastHistoryIndex].RawQuery); if (lastHistoryIndex > 1) { lastHistoryIndex--; @@ -537,7 +538,7 @@ namespace Flow.Launcher.ViewModel { if (Settings.ShowHistoryOnHomePage) { - _history.AddToHistory(result, Settings); + _history.AddToHistory(result, Settings.ShowHistoryQueryResultsForHomePage); } _userSelectedRecord.Add(result); @@ -615,7 +616,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - var historyItems = _history.GetHistoryItems(Settings); + var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. @@ -1301,7 +1302,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var items = _history.GetHistoryItems(Settings); + var items = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); var results = GetHistoryResults(items); @@ -1323,16 +1324,17 @@ namespace Flow.Launcher.ViewModel private List GetHistoryResults(IEnumerable historyItems) { var results = new List(); + foreach (var h in historyItems) { var result = new Result { Title = - Settings.ShowHistoryQueryResultsForHomePage ? Localize.executeQuery(h.OriginQuery.RawQuery) : h.Title, + Settings.ShowHistoryQueryResultsForHomePage ? Localize.executeQuery(h.RawQuery) : h.Title, SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), IcoPath = Constant.HistoryIcon, PluginID = h.PluginID, - OriginQuery = h.OriginQuery, + OriginQuery = QueryBuilder.Build(h.RawQuery, PluginManager.NonGlobalPlugins), Action = Settings.ShowHistoryLastOpenedResultsForHomePage ? h.ExecuteAction : h.QueryAction, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; @@ -1568,7 +1570,7 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _history.GetHistoryItems(Settings).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = GetHistoryResults(historyItems); From bf2acfec3880b9708b833e22e9a944ba14432524 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 02:54:29 -0300 Subject: [PATCH 083/125] feat: code quality --- Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 4 ++-- Flow.Launcher/Storage/History.cs | 2 -- Flow.Launcher/Storage/HistoryItem.cs | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 051326dbc..20b4fa016 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -237,7 +237,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } - private bool _showHistoryQueryResultsForHomePage = false; + private bool _showHistoryQueryResultsForHomePage = true; public bool ShowHistoryQueryResultsForHomePage { get => _showHistoryQueryResultsForHomePage; @@ -257,7 +257,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings } - private bool _showHistoryLastOpenedResultsForHomePage = false; + private bool _showHistoryLastOpenedResultsForHomePage; public bool ShowHistoryLastOpenedResultsForHomePage { get => _showHistoryLastOpenedResultsForHomePage; diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 0dd192558..b8a395088 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -30,7 +30,6 @@ namespace Flow.Launcher.Storage } - public List GetHistoryItems(bool isQuery) { if (isQuery) return PopulateActions(QueryHistoryItems, isQuery); @@ -115,7 +114,6 @@ namespace Flow.Launcher.Storage { Title = result.Title, SubTitle = result.SubTitle, - IcoPath = result.IcoPath ?? string.Empty, PluginID = result.PluginID, RawQuery = result.OriginQuery.RawQuery, ExecutedDateTime = DateTime.Now, diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index fb63aeffc..6a06a400a 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -7,7 +7,6 @@ public class HistoryItem { public string Title { get; set; } = string.Empty; public string SubTitle { get; set; } = string.Empty; - public string IcoPath { get; set; } = string.Empty; public string PluginID { get; set; } = string.Empty; public string RawQuery { get; set; } From 95122e91f528a24416e7911598801477529b8338 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 02:56:49 -0300 Subject: [PATCH 084/125] feat: code quality --- Flow.Launcher/Storage/History.cs | 3 ++- Flow.Launcher/Storage/HistoryLegacy.cs | 12 ------------ 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 Flow.Launcher/Storage/HistoryLegacy.cs diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index b8a395088..539ec64bb 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -30,6 +30,7 @@ namespace Flow.Launcher.Storage } + public List GetHistoryItems(bool isQuery) { if (isQuery) return PopulateActions(QueryHistoryItems, isQuery); @@ -121,7 +122,7 @@ namespace Flow.Launcher.Storage }; var existing = LastOpenedHistoryItems. - FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); + FirstOrDefault(x => x.Title == item.Title && x.SubTitle == item.SubTitle && x.PluginID == item.PluginID); if (existing != null) diff --git a/Flow.Launcher/Storage/HistoryLegacy.cs b/Flow.Launcher/Storage/HistoryLegacy.cs deleted file mode 100644 index 8311c0a8d..000000000 --- a/Flow.Launcher/Storage/HistoryLegacy.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace Flow.Launcher.Storage; -public class HistoryLegacy -{ - [JsonInclude] public List Items { get; private set; } = []; -} From e6cae1a79bd4d7fe6a38fcf8b616e256ff591e29 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 03:08:23 -0300 Subject: [PATCH 085/125] feat: helper --- Flow.Launcher/Storage/History.cs | 55 ++++++-------------------- Flow.Launcher/Storage/HistoryHelper.cs | 45 +++++++++++++++++++++ Flow.Launcher/Storage/HistoryItem.cs | 3 ++ 3 files changed, 60 insertions(+), 43 deletions(-) create mode 100644 Flow.Launcher/Storage/HistoryHelper.cs diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 539ec64bb..542bfbebc 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -using System.Threading; -using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage { - public class History { //Legacy @@ -30,11 +28,15 @@ namespace Flow.Launcher.Storage } - - public List GetHistoryItems(bool isQuery) + public List GetHistoryItems(Settings settings) { - if (isQuery) return PopulateActions(QueryHistoryItems, isQuery); - return PopulateActions(LastOpenedHistoryItems, isQuery); + if (settings.ShowHistoryOnHomePage) + { + if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems.PopulateActions(true); + return LastOpenedHistoryItems.PopulateActions(false); + } + + return new List(); } public void PopulateHistoryWithLegacyHistory() @@ -45,46 +47,13 @@ namespace Flow.Launcher.Storage { RawQuery = item.Query, ExecutedDateTime = item.ExecutedDateTime ?? DateTime.Now, - QueryAction = GetQueryAction(item.Query) + QueryAction = HistoryHelper.GetQueryAction(item.Query) }); } if (Items.Any()) Items.Clear(); } - private List PopulateActions(List items,bool isQuery) - { - - foreach (var item in items) - { - if (item.QueryAction != null && item.ExecuteAction != null) continue; - if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.RawQuery); - if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle); - } - - return items; - } - - private Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) - { - var plugin = PluginManager.GetPluginForId(pluginId); - - var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); - var freshResults = plugin.Plugin - .QueryAsync(query, CancellationToken.None) - .GetAwaiter() - .GetResult(); - return freshResults?.FirstOrDefault(r => r.Title == title - && r.SubTitle == subTitle)?.Action; - } - private Func GetQueryAction(string query) - { - return _=> - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(query); - return false; - }; - } + private void AddLastQuery(Result result) { @@ -104,7 +73,7 @@ namespace Flow.Launcher.Storage { RawQuery = result.OriginQuery.RawQuery, ExecutedDateTime = DateTime.Now, - QueryAction = GetQueryAction(result.OriginQuery.RawQuery) + QueryAction = HistoryHelper.GetQueryAction(result.OriginQuery.RawQuery) }); } } diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs new file mode 100644 index 000000000..9373a439c --- /dev/null +++ b/Flow.Launcher/Storage/HistoryHelper.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Storage; +public static class HistoryHelper +{ + internal static List PopulateActions(this List items, bool isQuery) + { + + foreach (var item in items) + { + if (item.QueryAction != null && item.ExecuteAction != null) continue; + if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.RawQuery); + if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle); + } + + return items; + } + + private static Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) + { + var plugin = PluginManager.GetPluginForId(pluginId); + + var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); + var freshResults = plugin.Plugin + .QueryAsync(query, CancellationToken.None) + .GetAwaiter() + .GetResult(); + return freshResults?.FirstOrDefault(r => r.Title == title + && r.SubTitle == subTitle)?.Action; + } + public static Func GetQueryAction(string query) + { + return _ => + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(query); + return false; + }; + } +} diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index 6a06a400a..04b97118e 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text.Json.Serialization; using Flow.Launcher.Plugin; @@ -16,4 +17,6 @@ public class HistoryItem [JsonIgnore] public Func QueryAction { get; set; } + + } From e468c48da4a4a13148d1eb6cc59a8b3ce666f1aa Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 03:15:10 -0300 Subject: [PATCH 086/125] feat: fix erros --- Flow.Launcher/Storage/History.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 542bfbebc..4ae24c202 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -91,7 +91,7 @@ namespace Flow.Launcher.Storage }; var existing = LastOpenedHistoryItems. - FirstOrDefault(x => x.Title == item.Title && x.SubTitle == item.SubTitle && x.PluginID == item.PluginID); + FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); if (existing != null) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index db67539aa..febb2d05e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -355,7 +355,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage).Count - 1; + History.SelectedIndex = _history.GetHistoryItems(Settings).Count - 1; } else { @@ -383,7 +383,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); + var historyItems = _history.GetHistoryItems(Settings); if (historyItems.Count > 0) { ChangeQueryText(historyItems[^lastHistoryIndex].RawQuery); @@ -397,7 +397,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); + var historyItems = _history.GetHistoryItems(Settings); if (historyItems.Count > 0) { @@ -616,7 +616,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); + var historyItems = _history.GetHistoryItems(Settings); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. @@ -1302,7 +1302,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var items = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage); + var items = _history.GetHistoryItems(Settings); var results = GetHistoryResults(items); @@ -1570,7 +1570,7 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _history.GetHistoryItems(Settings.ShowHistoryQueryResultsForHomePage).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _history.GetHistoryItems(Settings).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = GetHistoryResults(historyItems); From d7579cce9e85c934e0e33224e98bf1f0cef43596 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 03:23:14 -0300 Subject: [PATCH 087/125] fix erros --- Flow.Launcher/Storage/History.cs | 10 +++++----- Flow.Launcher/ViewModel/MainViewModel.cs | 6 +----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 4ae24c202..d99bfbfdb 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -16,14 +16,14 @@ namespace Flow.Launcher.Storage private int _maxHistory = 300; - public void AddToHistory(Result result, bool isQuery) + public void AddToHistory(Result result, Settings settings) { - if (isQuery) - { - AddLastQuery(result); + if (!settings.ShowHistoryOnHomePage) return; + if (settings.ShowHistoryQueryResultsForHomePage) + { + AddLastQuery(result); return; } - AddLastOpened(result); } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index febb2d05e..77fa6426d 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -536,11 +536,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { - if (Settings.ShowHistoryOnHomePage) - { - _history.AddToHistory(result, Settings.ShowHistoryQueryResultsForHomePage); - } - + _history.AddToHistory(result, Settings); _userSelectedRecord.Add(result); lastHistoryIndex = 1; } From 690d33ece31434103eb1e165a65361e82aaf1bcb Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 03:43:06 -0300 Subject: [PATCH 088/125] up --- Flow.Launcher/Storage/History.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index d99bfbfdb..a11222db8 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -39,7 +39,7 @@ namespace Flow.Launcher.Storage return new List(); } - public void PopulateHistoryWithLegacyHistory() + public void PopulateHistoryFromLegacyHistory() { foreach (var item in Items) { diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 77fa6426d..45c471527 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -153,7 +153,7 @@ namespace Flow.Launcher.ViewModel _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); _history = _historyStorage.Load(); - _history.PopulateHistoryWithLegacyHistory(); + _history.PopulateHistoryFromLegacyHistory(); _userSelectedRecord = _userSelectedRecordStorage.Load(); ContextMenu = new ResultsViewModel(Settings, this) From a1f82e1652a0a867291e8e70d75cac8ecf19cc0d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 03:47:04 -0300 Subject: [PATCH 089/125] refactor: using count for better performance --- Flow.Launcher/Storage/History.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index a11222db8..b0fd2a4c9 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -50,7 +50,7 @@ namespace Flow.Launcher.Storage QueryAction = HistoryHelper.GetQueryAction(item.Query) }); } - if (Items.Any()) Items.Clear(); + if (Items.Count > 0) Items.Clear(); } From a3b7c6808bb0b8be1ffba786493641c9bdec3792 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 04:29:33 -0300 Subject: [PATCH 090/125] up --- Flow.Launcher/MainWindow.xaml.cs | 1 + Flow.Launcher/Storage/HistoryHelper.cs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index d6bccac46..e05975ff8 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -322,6 +322,7 @@ namespace Flow.Launcher break; case nameof(Settings.ShowHomePage): case nameof(Settings.ShowHistoryQueryResultsForHomePage): + case nameof(Settings.ShowHistoryLastOpenedResultsForHomePage): if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText)) { _viewModel.QueryResults(); diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs index 9373a439c..7da77af2b 100644 --- a/Flow.Launcher/Storage/HistoryHelper.cs +++ b/Flow.Launcher/Storage/HistoryHelper.cs @@ -21,18 +21,6 @@ public static class HistoryHelper return items; } - private static Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) - { - var plugin = PluginManager.GetPluginForId(pluginId); - - var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); - var freshResults = plugin.Plugin - .QueryAsync(query, CancellationToken.None) - .GetAwaiter() - .GetResult(); - return freshResults?.FirstOrDefault(r => r.Title == title - && r.SubTitle == subTitle)?.Action; - } public static Func GetQueryAction(string query) { return _ => @@ -42,4 +30,16 @@ public static class HistoryHelper return false; }; } + + private static Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) + { + var plugin = PluginManager.GetPluginForId(pluginId); + var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); + var freshResults = plugin.Plugin + .QueryAsync(query, CancellationToken.None) + .GetAwaiter() + .GetResult(); + return freshResults?.FirstOrDefault(r => r.Title == title + && r.SubTitle == subTitle)?.Action; + } } From 2b7c2048ef007d851ec57321400a09cda5bca5f2 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Sat, 11 Oct 2025 05:26:28 -0300 Subject: [PATCH 091/125] up --- Flow.Launcher/Storage/History.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index b0fd2a4c9..346964f9f 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -30,13 +30,8 @@ namespace Flow.Launcher.Storage public List GetHistoryItems(Settings settings) { - if (settings.ShowHistoryOnHomePage) - { - if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems.PopulateActions(true); - return LastOpenedHistoryItems.PopulateActions(false); - } - - return new List(); + if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems.PopulateActions(true); + return LastOpenedHistoryItems.PopulateActions(false); } public void PopulateHistoryFromLegacyHistory() From e3527f47ea8f74119c63758a1437b72fd7f2a957 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 13 Oct 2025 15:27:06 +0800 Subject: [PATCH 092/125] Add RecordKey for precise history matching and refactor Added a `RecordKey` property to `HistoryItem` for unique identification of history records, enabling more accurate matching during queries and executions. Updated `HistoryHelper` methods to utilize `RecordKey` for matching, with fallback to `Title` and `SubTitle`. Enhanced `GetExecuteAction` with error handling, nullable reference types, and improved matching logic. Included `RecordKey` in `History` object creation. Enabled nullable reference types in `HistoryHelper.cs` for better code safety. Refactored code for clarity and maintainability. --- Flow.Launcher/Storage/History.cs | 1 + Flow.Launcher/Storage/HistoryHelper.cs | 44 +++++++++++++++++++------- Flow.Launcher/Storage/HistoryItem.cs | 1 + 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 346964f9f..5b69df056 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -81,6 +81,7 @@ namespace Flow.Launcher.Storage SubTitle = result.SubTitle, PluginID = result.PluginID, RawQuery = result.OriginQuery.RawQuery, + RecordKey = result.RecordKey, ExecutedDateTime = DateTime.Now, ExecuteAction = result.Action }; diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs index 7da77af2b..bb7f62eba 100644 --- a/Flow.Launcher/Storage/HistoryHelper.cs +++ b/Flow.Launcher/Storage/HistoryHelper.cs @@ -6,40 +6,62 @@ using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; + +#nullable enable + public static class HistoryHelper { - internal static List PopulateActions(this List items, bool isQuery) + internal static List PopulateActions(this List items, bool isQuery) { foreach (var item in items) { if (item.QueryAction != null && item.ExecuteAction != null) continue; if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.RawQuery); - if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle); + if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle, item.RecordKey) ?? GetQueryAction(item.RawQuery); } return items; } - public static Func GetQueryAction(string query) + public static Func GetQueryAction(string rawQuery) { return _ => { App.API.BackToQueryResults(); - App.API.ChangeQuery(query); + App.API.ChangeQuery(rawQuery); return false; }; } - private static Func GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle) + private static Func? GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle, string recordKey) { var plugin = PluginManager.GetPluginForId(pluginId); + if (plugin == null) return null; var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); - var freshResults = plugin.Plugin - .QueryAsync(query, CancellationToken.None) - .GetAwaiter() - .GetResult(); - return freshResults?.FirstOrDefault(r => r.Title == title - && r.SubTitle == subTitle)?.Action; + if (query == null) return null; + try + { +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + var freshResults = plugin.Plugin + .QueryAsync(query, CancellationToken.None) + .GetAwaiter() + .GetResult(); +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + // Try to match by record key first if it is valid, otherwise fall back to title + subtitle match + if (string.IsNullOrEmpty(recordKey)) + { + return freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle)?.Action; + } + else + { + return freshResults?.FirstOrDefault(r => r.RecordKey == recordKey)?.Action ?? + freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle)?.Action; + } + } + catch + { + return null; + } } } diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index 04b97118e..dc058c960 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -10,6 +10,7 @@ public class HistoryItem public string SubTitle { get; set; } = string.Empty; public string PluginID { get; set; } = string.Empty; public string RawQuery { get; set; } + public string RecordKey { get; set; } = string.Empty; public DateTime ExecutedDateTime { get; set; } [JsonIgnore] From 7fa78f03040620ee6188d481bb3c0b636a179e27 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 13 Oct 2025 15:30:38 +0800 Subject: [PATCH 093/125] Code cleanup --- Flow.Launcher/Storage/History.cs | 21 ++++++++++++--------- Flow.Launcher/Storage/HistoryItem.cs | 8 +++----- Flow.Launcher/Storage/HistoryItemLegacy.cs | 7 ++----- Flow.Launcher/ViewModel/MainViewModel.cs | 14 ++++++-------- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Flow.Launcher/Storage/History.cs b/Flow.Launcher/Storage/History.cs index 5b69df056..b7ee83480 100644 --- a/Flow.Launcher/Storage/History.cs +++ b/Flow.Launcher/Storage/History.cs @@ -10,9 +10,14 @@ namespace Flow.Launcher.Storage public class History { //Legacy - [JsonInclude] public List Items { get; private set; } = []; - [JsonInclude] public List LastOpenedHistoryItems { get; private set; } = []; - [JsonInclude] public List QueryHistoryItems { get; private set; } = []; + [JsonInclude] + public List Items { get; private set; } = []; + + [JsonInclude] + public List LastOpenedHistoryItems { get; private set; } = []; + + [JsonInclude] + public List QueryHistoryItems { get; private set; } = []; private int _maxHistory = 300; @@ -20,7 +25,7 @@ namespace Flow.Launcher.Storage { if (!settings.ShowHistoryOnHomePage) return; if (settings.ShowHistoryQueryResultsForHomePage) - { + { AddLastQuery(result); return; } @@ -28,7 +33,7 @@ namespace Flow.Launcher.Storage } - public List GetHistoryItems(Settings settings) + public List GetHistoryItems(Settings settings) { if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems.PopulateActions(true); return LastOpenedHistoryItems.PopulateActions(false); @@ -48,8 +53,6 @@ namespace Flow.Launcher.Storage if (Items.Count > 0) Items.Clear(); } - - private void AddLastQuery(Result result) { if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; @@ -83,11 +86,11 @@ namespace Flow.Launcher.Storage RawQuery = result.OriginQuery.RawQuery, RecordKey = result.RecordKey, ExecutedDateTime = DateTime.Now, - ExecuteAction = result.Action + ExecuteAction = result.Action }; var existing = LastOpenedHistoryItems. - FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); + FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); if (existing != null) diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index dc058c960..dcace8130 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; + public class HistoryItem { public string Title { get; set; } = string.Empty; @@ -11,13 +11,11 @@ public class HistoryItem public string PluginID { get; set; } = string.Empty; public string RawQuery { get; set; } public string RecordKey { get; set; } = string.Empty; - public DateTime ExecutedDateTime { get; set; } + [JsonIgnore] public Func ExecuteAction { get; set; } + [JsonIgnore] public Func QueryAction { get; set; } - - - } diff --git a/Flow.Launcher/Storage/HistoryItemLegacy.cs b/Flow.Launcher/Storage/HistoryItemLegacy.cs index 1ae62ff9e..e1e0a12ae 100644 --- a/Flow.Launcher/Storage/HistoryItemLegacy.cs +++ b/Flow.Launcher/Storage/HistoryItemLegacy.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Flow.Launcher.Storage; + +[Obsolete] public class HistoryItemLegacy { public string Query { get; set; } public DateTime? ExecutedDateTime { get; set; } - } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 45c471527..f378ff39f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -8,8 +8,8 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using System.Windows; -using System.Windows.Input; using System.Windows.Controls; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; @@ -1280,9 +1280,9 @@ namespace Flow.Launcher.ViewModel if (!match.IsSearchPrecisionScoreMet()) return false; - r.Score = match.Score; - return true; - }).ToList(); + r.Score = match.Score; + return true; + }).ToList(); ContextMenu.AddResults(filtered, id); } else @@ -1308,7 +1308,7 @@ namespace Flow.Launcher.ViewModel ( r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() - ).ToList(); + ).ToList(); History.AddResults(filtered, id); } else @@ -1320,7 +1320,6 @@ namespace Flow.Launcher.ViewModel private List GetHistoryResults(IEnumerable historyItems) { var results = new List(); - foreach (var h in historyItems) { var result = new Result @@ -1336,11 +1335,10 @@ namespace Flow.Launcher.ViewModel }; results.Add(result); } - return results; } - private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) + private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); From b84ca9b28373289fc7858a6f3f7c6f217d46bd9a Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 19:39:33 +0800 Subject: [PATCH 094/125] Improve code quality --- Flow.Launcher/Storage/HistoryHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs index bb7f62eba..2f58b5bb7 100644 --- a/Flow.Launcher/Storage/HistoryHelper.cs +++ b/Flow.Launcher/Storage/HistoryHelper.cs @@ -13,7 +13,6 @@ public static class HistoryHelper { internal static List PopulateActions(this List items, bool isQuery) { - foreach (var item in items) { if (item.QueryAction != null && item.ExecuteAction != null) continue; From f6d5a27e0b5169bdcf1d239bd24f92f8b13ac790 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 20:28:39 +0800 Subject: [PATCH 095/125] Refactor and enhance history management system Refactored the `History` class to separate query and last opened history into distinct models, introducing `LastOpenedHistoryItem` and deprecating `HistoryItem`. Added a `HistoryStyle` enum and property to allow users to toggle between "Query History" and "Last Opened History." Simplified history display logic and updated the UI with a dropdown for history style selection. Improved history item display with dynamic action population and relative timestamps. Enhanced performance by optimizing history storage with a maximum limit and streamlined logic for adding/retrieving items. Ensured backward compatibility by migrating legacy history data to the new format. Updated localization strings, removed deprecated properties, and cleaned up redundant code. Fixed bugs related to inconsistent history actions and edge cases with legacy data. --- .../UserSettings/Settings.cs | 85 +++++--------- Flow.Launcher/Languages/en.xaml | 13 +- Flow.Launcher/MainWindow.xaml.cs | 4 +- .../SettingsPaneGeneralViewModel.cs | 3 + .../Views/SettingsPaneGeneral.xaml | 30 +++-- Flow.Launcher/Storage/History.cs | 111 ------------------ Flow.Launcher/Storage/HistoryHelper.cs | 6 +- Flow.Launcher/Storage/HistoryItem.cs | 55 ++++++--- Flow.Launcher/Storage/HistoryItemLegacy.cs | 10 -- .../Storage/LastOpenedHistoryItem.cs | 38 ++++++ Flow.Launcher/Storage/QueryHistory.cs | 76 ++++++++++++ Flow.Launcher/ViewModel/MainViewModel.cs | 56 +++++---- 12 files changed, 247 insertions(+), 240 deletions(-) delete mode 100644 Flow.Launcher/Storage/History.cs delete mode 100644 Flow.Launcher/Storage/HistoryItemLegacy.cs create mode 100644 Flow.Launcher/Storage/LastOpenedHistoryItem.cs create mode 100644 Flow.Launcher/Storage/QueryHistory.cs diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 20b4fa016..6adefdb6a 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.Storage; +using Flow.Launcher.Localization.Attributes; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; @@ -214,64 +215,17 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } } - private bool _showHistoryOnHomePage = true; - public bool ShowHistoryOnHomePage + + private bool _showHistoryResultsForHomePage = false; + public bool ShowHistoryResultsForHomePage { - get - { - if (ShowHistoryQueryResultsForHomePage || ShowHistoryLastOpenedResultsForHomePage) return true; - return _showHistoryOnHomePage; - } + get => _showHistoryResultsForHomePage; set { - if (_showHistoryOnHomePage != value) + if (_showHistoryResultsForHomePage != value) { - _showHistoryOnHomePage = value; + _showHistoryResultsForHomePage = value; OnPropertyChanged(); - if (value == false) - { - ShowHistoryQueryResultsForHomePage = false; - ShowHistoryLastOpenedResultsForHomePage = false; - } - } - } - } - - private bool _showHistoryQueryResultsForHomePage = true; - public bool ShowHistoryQueryResultsForHomePage - { - get => _showHistoryQueryResultsForHomePage; - set - { - if (_showHistoryQueryResultsForHomePage != value) - { - _showHistoryQueryResultsForHomePage = value; - OnPropertyChanged(); - if (value && _showHistoryLastOpenedResultsForHomePage) - { - _showHistoryLastOpenedResultsForHomePage = false; - OnPropertyChanged(nameof(ShowHistoryLastOpenedResultsForHomePage)); - } - } - } - } - - - private bool _showHistoryLastOpenedResultsForHomePage; - public bool ShowHistoryLastOpenedResultsForHomePage - { - get => _showHistoryLastOpenedResultsForHomePage; - set - { - if (_showHistoryLastOpenedResultsForHomePage != value) - { - _showHistoryLastOpenedResultsForHomePage = value; - OnPropertyChanged(); - if (value && _showHistoryQueryResultsForHomePage) - { - _showHistoryQueryResultsForHomePage = false; - OnPropertyChanged(nameof(ShowHistoryQueryResultsForHomePage)); - } } } } @@ -560,6 +514,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings [JsonConverter(typeof(JsonStringEnumConverter))] public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected; + private HistoryStyle _historyStyle = HistoryStyle.Query; + [JsonConverter(typeof(JsonStringEnumConverter))] + public HistoryStyle HistoryStyle + { + get => _historyStyle; + set + { + if (_historyStyle != value) + { + _historyStyle = value; + OnPropertyChanged(); + } + } + } + [JsonConverter(typeof(JsonStringEnumConverter))] public AnimationSpeeds AnimationSpeed { get; set; } = AnimationSpeeds.Medium; public int CustomAnimationLength { get; set; } = 360; @@ -742,4 +711,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings FullPathOpen, Directory } + + [EnumLocalize] + public enum HistoryStyle + { + [EnumLocalizeKey(nameof(Localize.queryHistory))] + Query, + + [EnumLocalizeKey(nameof(Localize.executedHistory))] + LastOpened + } } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 0f44d77df..dd35bcc1d 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -164,13 +164,12 @@ Please check your system registry access or contact support. Home Page Show home page results when query text is empty. - Home Page History - Choose the type of history to show on the home page - Query History - Last Opened History - Show Query History - Show Last Opened History - Maximum History Results Shown + Show History Results in Home Page + Maximum History Results Shown in Home Page + History Style + Choose the type of history to show in the history and home page + Query history + Last opened history This can only be edited if plugin supports Home feature and Home Page is enabled. Show Search Window at Foremost Overrides other programs' 'Always on Top' setting and displays Flow in the foremost position. diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index e05975ff8..88d6b6606 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -321,8 +321,8 @@ namespace Flow.Launcher InitializeContextMenu(); break; case nameof(Settings.ShowHomePage): - case nameof(Settings.ShowHistoryQueryResultsForHomePage): - case nameof(Settings.ShowHistoryLastOpenedResultsForHomePage): + case nameof(Settings.ShowHistoryResultsForHomePage): + case nameof(Settings.HistoryStyle): if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText)) { _viewModel.QueryResults(); diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index 6641ac689..aa78849ba 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -147,6 +147,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel public List LastQueryModes { get; } = DropdownDataGeneric.GetValues("LastQuery"); + public List HistoryStyles { get; } = HistoryStyleLocalized.GetValues(); + public bool EnableDialogJump { get => Settings.EnableDialogJump; @@ -213,6 +215,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel DropdownDataGeneric.UpdateLabels(SearchWindowAligns); DropdownDataGeneric.UpdateLabels(SearchPrecisionScores); DropdownDataGeneric.UpdateLabels(LastQueryModes); + HistoryStyleLocalized.UpdateLabels(HistoryStyles); DropdownDataGeneric.UpdateLabels(DoublePinyinSchemas); DropdownDataGeneric.UpdateLabels(DialogJumpWindowPositions); DropdownDataGeneric.UpdateLabels(DialogJumpResultBehaviours); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index ddd7747d1..0c48b511a 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -13,7 +13,7 @@ xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure" Title="General" d:DataContext="{d:DesignInstance settingsViewModels:SettingsPaneGeneralViewModel}" - d:DesignHeight="450" + d:DesignHeight="10000" d:DesignWidth="800" mc:Ignorable="d"> @@ -245,6 +245,22 @@ SelectedValuePath="Value" /> + + + + + + + + - + - - - - - - - + Items { get; private set; } = []; - - [JsonInclude] - public List LastOpenedHistoryItems { get; private set; } = []; - - [JsonInclude] - public List QueryHistoryItems { get; private set; } = []; - - private int _maxHistory = 300; - - public void AddToHistory(Result result, Settings settings) - { - if (!settings.ShowHistoryOnHomePage) return; - if (settings.ShowHistoryQueryResultsForHomePage) - { - AddLastQuery(result); - return; - } - AddLastOpened(result); - } - - - public List GetHistoryItems(Settings settings) - { - if (settings.ShowHistoryQueryResultsForHomePage) return QueryHistoryItems.PopulateActions(true); - return LastOpenedHistoryItems.PopulateActions(false); - } - - public void PopulateHistoryFromLegacyHistory() - { - foreach (var item in Items) - { - QueryHistoryItems.Add(new HistoryItem - { - RawQuery = item.Query, - ExecutedDateTime = item.ExecutedDateTime ?? DateTime.Now, - QueryAction = HistoryHelper.GetQueryAction(item.Query) - }); - } - if (Items.Count > 0) Items.Clear(); - } - - private void AddLastQuery(Result result) - { - if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; - if (QueryHistoryItems.Count > _maxHistory) - { - QueryHistoryItems.RemoveAt(0); - } - - if (QueryHistoryItems.Count > 0 && QueryHistoryItems.Last().RawQuery == result.OriginQuery.RawQuery) - { - QueryHistoryItems.Last().ExecutedDateTime = DateTime.Now; - } - else - { - QueryHistoryItems.Add(new HistoryItem - { - RawQuery = result.OriginQuery.RawQuery, - ExecutedDateTime = DateTime.Now, - QueryAction = HistoryHelper.GetQueryAction(result.OriginQuery.RawQuery) - }); - } - } - - private void AddLastOpened(Result result) - { - var item = new HistoryItem - { - Title = result.Title, - SubTitle = result.SubTitle, - PluginID = result.PluginID, - RawQuery = result.OriginQuery.RawQuery, - RecordKey = result.RecordKey, - ExecutedDateTime = DateTime.Now, - ExecuteAction = result.Action - }; - - var existing = LastOpenedHistoryItems. - FirstOrDefault(x => x.Title == item.Title && x.PluginID == item.PluginID); - - - if (existing != null) - { - existing.ExecutedDateTime = DateTime.Now; - } - else - { - if (LastOpenedHistoryItems.Count > _maxHistory) - { - LastOpenedHistoryItems.RemoveAt(0); - } - - LastOpenedHistoryItems.Add(item); - } - } - } -} diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs index 2f58b5bb7..d9d0ec5a8 100644 --- a/Flow.Launcher/Storage/HistoryHelper.cs +++ b/Flow.Launcher/Storage/HistoryHelper.cs @@ -11,13 +11,13 @@ namespace Flow.Launcher.Storage; public static class HistoryHelper { - internal static List PopulateActions(this List items, bool isQuery) + internal static List PopulateActions(this List items, bool isQuery) { foreach (var item in items) { if (item.QueryAction != null && item.ExecuteAction != null) continue; - if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.RawQuery); - if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.RawQuery, item.Title, item.SubTitle, item.RecordKey) ?? GetQueryAction(item.RawQuery); + if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.Query); + if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey) ?? GetQueryAction(item.Query); } return items; diff --git a/Flow.Launcher/Storage/HistoryItem.cs b/Flow.Launcher/Storage/HistoryItem.cs index dcace8130..63604d2c8 100644 --- a/Flow.Launcher/Storage/HistoryItem.cs +++ b/Flow.Launcher/Storage/HistoryItem.cs @@ -1,21 +1,46 @@ using System; -using System.Text.Json.Serialization; -using Flow.Launcher.Plugin; -namespace Flow.Launcher.Storage; - -public class HistoryItem +namespace Flow.Launcher.Storage { - public string Title { get; set; } = string.Empty; - public string SubTitle { get; set; } = string.Empty; - public string PluginID { get; set; } = string.Empty; - public string RawQuery { get; set; } - public string RecordKey { get; set; } = string.Empty; - public DateTime ExecutedDateTime { get; set; } + [Obsolete("Use LastOpenedHistoryItem instead. This class will be removed in future versions.")] + public class HistoryItem + { + public string Query { get; set; } + public DateTime ExecutedDateTime { get; set; } - [JsonIgnore] - public Func ExecuteAction { get; set; } + public string GetTimeAgo() + { + return DateTimeAgo(ExecutedDateTime); + } - [JsonIgnore] - public Func QueryAction { get; set; } + private string DateTimeAgo(DateTime dt) + { + var span = DateTime.Now - dt; + if (span.Days > 365) + { + int years = (span.Days / 365); + if (span.Days % 365 != 0) + years += 1; + return $"about {years} {(years == 1 ? "year" : "years")} ago"; + } + if (span.Days > 30) + { + int months = (span.Days / 30); + if (span.Days % 31 != 0) + months += 1; + return $"about {months} {(months == 1 ? "month" : "months")} ago"; + } + if (span.Days > 0) + return $"about {span.Days} {(span.Days == 1 ? "day" : "days")} ago"; + if (span.Hours > 0) + return $"about {span.Hours} {(span.Hours == 1 ? "hour" : "hours")} ago"; + if (span.Minutes > 0) + return $"about {span.Minutes} {(span.Minutes == 1 ? "minute" : "minutes")} ago"; + if (span.Seconds > 5) + return $"about {span.Seconds} seconds ago"; + if (span.Seconds <= 5) + return "just now"; + return string.Empty; + } + } } diff --git a/Flow.Launcher/Storage/HistoryItemLegacy.cs b/Flow.Launcher/Storage/HistoryItemLegacy.cs deleted file mode 100644 index e1e0a12ae..000000000 --- a/Flow.Launcher/Storage/HistoryItemLegacy.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Flow.Launcher.Storage; - -[Obsolete] -public class HistoryItemLegacy -{ - public string Query { get; set; } - public DateTime? ExecutedDateTime { get; set; } -} diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs new file mode 100644 index 000000000..c09933cc9 --- /dev/null +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -0,0 +1,38 @@ +using System; +using System.Text.Json.Serialization; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Storage; + +public class LastOpenedHistoryItem +{ + public string Title { get; set; } = string.Empty; + public string SubTitle { get; set; } = string.Empty; + public string PluginID { get; set; } = string.Empty; + public string Query { get; set; } = string.Empty; + public string RecordKey { get; set; } = string.Empty; + public DateTime ExecutedDateTime { get; set; } + + [JsonIgnore] + public Func ExecuteAction { get; set; } + + [JsonIgnore] + public Func QueryAction { get; set; } + + public bool Equals(Result r) + { + if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey)) + { + return Title == r.Title + && SubTitle == r.SubTitle + && PluginID == r.PluginID + && Query == r.OriginQuery.RawQuery; + } + else + { + return RecordKey == r.RecordKey + && PluginID == r.PluginID + && Query == r.OriginQuery.RawQuery; + } + } +} diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs new file mode 100644 index 000000000..bd23837f8 --- /dev/null +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Storage +{ + public class History + { + [JsonInclude] +#pragma warning disable CS0618 // Type or member is obsolete + public List Items { get; private set; } = []; +#pragma warning restore CS0618 // Type or member is obsolete + + [JsonInclude] + public List LastOpenedHistoryItems { get; private set; } = []; + + private readonly int _maxHistory = 300; + private static readonly Settings _settings = Ioc.Default.GetRequiredService(); + + public void PopulateHistoryFromLegacyHistory() + { + if (Items.Count == 0) return; + // Migrate old history items to new LastOpenedHistoryItems + foreach (var item in Items) + { + LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + { + Query = item.Query, + ExecutedDateTime = item.ExecutedDateTime, + QueryAction = HistoryHelper.GetQueryAction(item.Query) + }); + } + Items.Clear(); + } + + public void Add(Result result) + { + if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; + + // Maintain the max history limit + if (LastOpenedHistoryItems.Count > _maxHistory) + { + LastOpenedHistoryItems.RemoveAt(0); + } + + // If the last item is the same as the current result, just update the timestamp + if (LastOpenedHistoryItems.Count > 0 && + LastOpenedHistoryItems.Last().Equals(result)) + { + LastOpenedHistoryItems.Last().ExecutedDateTime = DateTime.Now; + } + else + { + LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + { + Title = result.Title, + SubTitle = result.SubTitle, + PluginID = result.PluginID, + Query = result.OriginQuery.RawQuery, + RecordKey = result.RecordKey, + ExecutedDateTime = DateTime.Now, + ExecuteAction = result.Action + }); + } + } + + public List GetHistoryItems() + { + return LastOpenedHistoryItems.PopulateActions(_settings.HistoryStyle == HistoryStyle.Query); + } + } +} diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f378ff39f..846b46a43 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -40,7 +40,7 @@ namespace Flow.Launcher.ViewModel private string _queryTextBeforeLeaveResults; private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results - private readonly FlowLauncherJsonStorage _historyStorage; + private readonly FlowLauncherJsonStorage _historyItemsStorage; private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord; private readonly History _history; @@ -147,12 +147,10 @@ namespace Flow.Launcher.ViewModel } }; - _historyStorage = new FlowLauncherJsonStorage(); - + _historyItemsStorage = new FlowLauncherJsonStorage(); _userSelectedRecordStorage = new FlowLauncherJsonStorage(); _topMostRecord = new FlowLauncherJsonStorageTopMostRecord(); - - _history = _historyStorage.Load(); + _history = _historyItemsStorage.Load(); _history.PopulateHistoryFromLegacyHistory(); _userSelectedRecord = _userSelectedRecordStorage.Load(); @@ -355,7 +353,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _history.GetHistoryItems(Settings).Count - 1; + History.SelectedIndex = _history.GetHistoryItems().Count - 1; } else { @@ -383,10 +381,10 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - var historyItems = _history.GetHistoryItems(Settings); + var historyItems = _history.GetHistoryItems(); if (historyItems.Count > 0) { - ChangeQueryText(historyItems[^lastHistoryIndex].RawQuery); + ChangeQueryText(historyItems[^lastHistoryIndex].Query); if (lastHistoryIndex < historyItems.Count) { lastHistoryIndex++; @@ -397,11 +395,10 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - var historyItems = _history.GetHistoryItems(Settings); - + var historyItems = _history.GetHistoryItems(); if (historyItems.Count > 0) { - ChangeQueryText(historyItems[^lastHistoryIndex].RawQuery); + ChangeQueryText(historyItems[^lastHistoryIndex].Query); if (lastHistoryIndex > 1) { lastHistoryIndex--; @@ -533,11 +530,10 @@ namespace Flow.Launcher.ViewModel Hide(); } } - if (QueryResultsSelected()) { - _history.AddToHistory(result, Settings); _userSelectedRecord.Add(result); + _history.Add(result); lastHistoryIndex = 1; } } @@ -566,6 +562,7 @@ namespace Flow.Launcher.ViewModel resultsCopy.Add(resultCopy); } } + return resultsCopy; } @@ -612,11 +609,11 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - var historyItems = _history.GetHistoryItems(Settings); + var historyItems = _history.GetHistoryItems(); if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. - && historyItems.Count > 0) + && historyItems.Count > 0) // Have history items { lastHistoryIndex = 1; ReverseHistory(); @@ -1298,9 +1295,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var items = _history.GetHistoryItems(Settings); - - var results = GetHistoryResults(items); + var results = GetHistoryItems(_history.GetHistoryItems()); if (!string.IsNullOrEmpty(query)) { @@ -1317,20 +1312,22 @@ namespace Flow.Launcher.ViewModel } } - private List GetHistoryResults(IEnumerable historyItems) + private List GetHistoryItems(IEnumerable historyItems) { var results = new List(); foreach (var h in historyItems) { var result = new Result { - Title = - Settings.ShowHistoryQueryResultsForHomePage ? Localize.executeQuery(h.RawQuery) : h.Title, + Title = Settings.HistoryStyle == HistoryStyle.Query ? + Localize.executeQuery(h.Query) : + string.IsNullOrEmpty(h.Title) ? // Old migrated history items have no title + Localize.executeQuery(h.Query) : + h.Title, SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), IcoPath = Constant.HistoryIcon, - PluginID = h.PluginID, - OriginQuery = QueryBuilder.Build(h.RawQuery, PluginManager.NonGlobalPlugins), - Action = Settings.ShowHistoryLastOpenedResultsForHomePage ? h.ExecuteAction : h.QueryAction, + OriginQuery = new Query { RawQuery = h.Query }, + Action = Settings.HistoryStyle == HistoryStyle.Query ? h.QueryAction : h.ExecuteAction, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; results.Add(result); @@ -1452,7 +1449,8 @@ namespace Flow.Launcher.ViewModel true => Task.CompletedTask }).ToArray(); - if (Settings.ShowHistoryOnHomePage) + // Query history results for home page firstly so it will be put on top of the results + if (Settings.ShowHistoryResultsForHomePage) { QueryHistoryTask(currentCancellationToken); } @@ -1564,9 +1562,9 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _history.GetHistoryItems(Settings).TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _history.GetHistoryItems().TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); - var results = GetHistoryResults(historyItems); + var results = GetHistoryItems(historyItems); if (token.IsCancellationRequested) return; @@ -1702,7 +1700,7 @@ namespace Flow.Launcher.ViewModel /// True if existing results should be cleared, false otherwise. private bool ShouldClearExistingResultsForNonQuery(ICollection plugins) { - if (!Settings.ShowHistoryQueryResultsForHomePage && (plugins.Count == 0 || plugins.All(x => x.Metadata.HomeDisabled == true))) + if (!Settings.ShowHistoryResultsForHomePage && (plugins.Count == 0 || plugins.All(x => x.Metadata.HomeDisabled == true))) { App.API.LogDebug(ClassName, $"Existing results should be cleared for non-query"); return true; @@ -2166,7 +2164,7 @@ namespace Flow.Launcher.ViewModel /// public void Save() { - _historyStorage.Save(); + _historyItemsStorage.Save(); _userSelectedRecordStorage.Save(); _topMostRecord.Save(); } From 693bae763138d6ea865f413a5eb7064423437f89 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 20:47:57 +0800 Subject: [PATCH 096/125] Optimize query result selection handling Refactored `QueryResultsSelected()` usage by introducing a local variable `queryResultsSelected` to avoid redundant method calls, improving efficiency and readability. Added a comment to clarify the purpose of the variable. Updated conditional logic to use the new variable instead of directly invoking the method. --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 846b46a43..ceced2d18 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -491,6 +491,8 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private async Task OpenResultAsync(string index) { + // Must check query results selected before executing the action + var queryResultsSelected = QueryResultsSelected(); var results = SelectedResults; if (index is not null) { @@ -530,7 +532,7 @@ namespace Flow.Launcher.ViewModel Hide(); } } - if (QueryResultsSelected()) + if (queryResultsSelected) { _userSelectedRecord.Add(result); _history.Add(result); From 787ccad3841849d106a6cdf289b556a14f927ab9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 21:29:22 +0800 Subject: [PATCH 097/125] Refactor history handling with async ResultHelper Replaced `HistoryHelper` with a new `ResultHelper` class to handle plugin result population asynchronously, improving performance and maintainability. Removed `ExecuteAction` and `QueryAction` properties from `LastOpenedHistoryItem` and updated `QueryHistory` and `MainViewModel` to use `LastOpenedHistoryItems` directly. Refactored history result generation to support `AsyncAction` in `Result` objects, replacing synchronous plugin queries. Simplified legacy history migration and enhanced support for `HistoryStyle`. Improved error handling, code readability, and UI-related logic for history navigation. --- Flow.Launcher/Helper/ResultHelper.cs | 44 ++++++++++ Flow.Launcher/Storage/HistoryHelper.cs | 66 --------------- .../Storage/LastOpenedHistoryItem.cs | 7 -- Flow.Launcher/Storage/QueryHistory.cs | 14 +--- Flow.Launcher/ViewModel/MainViewModel.cs | 80 ++++++++++++++----- 5 files changed, 108 insertions(+), 103 deletions(-) create mode 100644 Flow.Launcher/Helper/ResultHelper.cs delete mode 100644 Flow.Launcher/Storage/HistoryHelper.cs diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs new file mode 100644 index 000000000..f99ba0377 --- /dev/null +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -0,0 +1,44 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Plugin; +using Flow.Launcher.Storage; + +namespace Flow.Launcher.Helper; + +#nullable enable + +public static class ResultHelper +{ + public static async Task PopulateResultsAsync(LastOpenedHistoryItem item) + { + return await PopulateResultsAsync(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey); + } + + public static async Task PopulateResultsAsync(string pluginId, string rawQuery, string title, string subTitle, string recordKey) + { + var plugin = PluginManager.GetPluginForId(pluginId); + if (plugin == null) return null; + var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); + if (query == null) return null; + try + { + var freshResults = await plugin.Plugin.QueryAsync(query, CancellationToken.None); + // Try to match by record key first if it is valid, otherwise fall back to title + subtitle match + if (string.IsNullOrEmpty(recordKey)) + { + return freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle); + } + else + { + return freshResults?.FirstOrDefault(r => r.RecordKey == recordKey) ?? + freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle); + } + } + catch + { + return null; + } + } +} diff --git a/Flow.Launcher/Storage/HistoryHelper.cs b/Flow.Launcher/Storage/HistoryHelper.cs deleted file mode 100644 index d9d0ec5a8..000000000 --- a/Flow.Launcher/Storage/HistoryHelper.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Plugin; - -namespace Flow.Launcher.Storage; - -#nullable enable - -public static class HistoryHelper -{ - internal static List PopulateActions(this List items, bool isQuery) - { - foreach (var item in items) - { - if (item.QueryAction != null && item.ExecuteAction != null) continue; - if (isQuery && item.QueryAction == null) item.QueryAction = GetQueryAction(item.Query); - if (!isQuery && item.ExecuteAction == null) item.ExecuteAction = GetExecuteAction(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey) ?? GetQueryAction(item.Query); - } - - return items; - } - - public static Func GetQueryAction(string rawQuery) - { - return _ => - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(rawQuery); - return false; - }; - } - - private static Func? GetExecuteAction(string pluginId, string rawQuery, string title, string subTitle, string recordKey) - { - var plugin = PluginManager.GetPluginForId(pluginId); - if (plugin == null) return null; - var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); - if (query == null) return null; - try - { -#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - var freshResults = plugin.Plugin - .QueryAsync(query, CancellationToken.None) - .GetAwaiter() - .GetResult(); -#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits - // Try to match by record key first if it is valid, otherwise fall back to title + subtitle match - if (string.IsNullOrEmpty(recordKey)) - { - return freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle)?.Action; - } - else - { - return freshResults?.FirstOrDefault(r => r.RecordKey == recordKey)?.Action ?? - freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle)?.Action; - } - } - catch - { - return null; - } - } -} diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs index c09933cc9..47647066c 100644 --- a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -1,5 +1,4 @@ using System; -using System.Text.Json.Serialization; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; @@ -13,12 +12,6 @@ public class LastOpenedHistoryItem public string RecordKey { get; set; } = string.Empty; public DateTime ExecutedDateTime { get; set; } - [JsonIgnore] - public Func ExecuteAction { get; set; } - - [JsonIgnore] - public Func QueryAction { get; set; } - public bool Equals(Result r) { if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey)) diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index bd23837f8..7d264d09f 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage @@ -19,7 +17,6 @@ namespace Flow.Launcher.Storage public List LastOpenedHistoryItems { get; private set; } = []; private readonly int _maxHistory = 300; - private static readonly Settings _settings = Ioc.Default.GetRequiredService(); public void PopulateHistoryFromLegacyHistory() { @@ -30,8 +27,7 @@ namespace Flow.Launcher.Storage LastOpenedHistoryItems.Add(new LastOpenedHistoryItem { Query = item.Query, - ExecutedDateTime = item.ExecutedDateTime, - QueryAction = HistoryHelper.GetQueryAction(item.Query) + ExecutedDateTime = item.ExecutedDateTime }); } Items.Clear(); @@ -62,15 +58,9 @@ namespace Flow.Launcher.Storage PluginID = result.PluginID, Query = result.OriginQuery.RawQuery, RecordKey = result.RecordKey, - ExecutedDateTime = DateTime.Now, - ExecuteAction = result.Action + ExecutedDateTime = DateTime.Now }); } } - - public List GetHistoryItems() - { - return LastOpenedHistoryItems.PopulateActions(_settings.HistoryStyle == HistoryStyle.Query); - } } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ceced2d18..8fd6de6f5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -15,6 +15,7 @@ using System.Windows.Threading; using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.DialogJump; using Flow.Launcher.Infrastructure.Hotkey; @@ -353,7 +354,7 @@ namespace Flow.Launcher.ViewModel if (QueryResultsSelected()) { SelectedResults = History; - History.SelectedIndex = _history.GetHistoryItems().Count - 1; + History.SelectedIndex = _history.LastOpenedHistoryItems.Count - 1; } else { @@ -381,7 +382,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ReverseHistory() { - var historyItems = _history.GetHistoryItems(); + var historyItems = _history.LastOpenedHistoryItems; if (historyItems.Count > 0) { ChangeQueryText(historyItems[^lastHistoryIndex].Query); @@ -395,7 +396,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] public void ForwardHistory() { - var historyItems = _history.GetHistoryItems(); + var historyItems = _history.LastOpenedHistoryItems; if (historyItems.Count > 0) { ChangeQueryText(historyItems[^lastHistoryIndex].Query); @@ -611,7 +612,7 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - var historyItems = _history.GetHistoryItems(); + var historyItems = _history.LastOpenedHistoryItems; if (QueryResultsSelected() // Results selected && string.IsNullOrEmpty(QueryText) // No input && Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed. @@ -1297,7 +1298,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var results = GetHistoryItems(_history.GetHistoryItems()); + var results = GetHistoryItems(_history.LastOpenedHistoryItems); if (!string.IsNullOrEmpty(query)) { @@ -1317,22 +1318,65 @@ namespace Flow.Launcher.ViewModel private List GetHistoryItems(IEnumerable historyItems) { var results = new List(); - foreach (var h in historyItems) + if (Settings.HistoryStyle == HistoryStyle.Query) { - var result = new Result + foreach (var h in historyItems) { - Title = Settings.HistoryStyle == HistoryStyle.Query ? - Localize.executeQuery(h.Query) : - string.IsNullOrEmpty(h.Title) ? // Old migrated history items have no title + var result = new Result + { + Title = Localize.executeQuery(h.Query), + SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), + IcoPath = Constant.HistoryIcon, + OriginQuery = new Query { RawQuery = h.Query }, + Action = _ => + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(h.Query); + return false; + }, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") + }; + results.Add(result); + } + } + else + { + foreach (var h in historyItems) + { + var result = new Result + { + Title = string.IsNullOrEmpty(h.Title) ? // Old migrated history items have no title Localize.executeQuery(h.Query) : h.Title, - SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), - IcoPath = Constant.HistoryIcon, - OriginQuery = new Query { RawQuery = h.Query }, - Action = Settings.HistoryStyle == HistoryStyle.Query ? h.QueryAction : h.ExecuteAction, - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") - }; - results.Add(result); + SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime), + IcoPath = Constant.HistoryIcon, + OriginQuery = new Query { RawQuery = h.Query }, + AsyncAction = async c => + { + var reflectResult = await ResultHelper.PopulateResultsAsync(h); + if (reflectResult != null) + { + if (reflectResult.Action != null) + { + reflectResult.Action(c); + } + else if (reflectResult.AsyncAction != null) + { + await reflectResult.AsyncAction(c); + } + return false; + } + else + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(h.Query); + return false; + } + }, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") + }; + results.Add(result); + } } return results; } @@ -1564,7 +1608,7 @@ namespace Flow.Launcher.ViewModel void QueryHistoryTask(CancellationToken token) { // Select last history results and revert its order to make sure last history results are on top - var historyItems = _history.GetHistoryItems().TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); + var historyItems = _history.LastOpenedHistoryItems.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse(); var results = GetHistoryItems(historyItems); From 66fe0b824522030d83fb56d6df2ea786aa8624a3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 14 Oct 2025 23:23:52 +0800 Subject: [PATCH 098/125] Adjust design-time height in SettingsPaneGeneral.xaml --- Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index 0c48b511a..720cb440b 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -13,7 +13,7 @@ xmlns:userSettings="clr-namespace:Flow.Launcher.Infrastructure.UserSettings;assembly=Flow.Launcher.Infrastructure" Title="General" d:DataContext="{d:DesignInstance settingsViewModels:SettingsPaneGeneralViewModel}" - d:DesignHeight="10000" + d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> From a3d1c435809cb537708a6db23908caea62ab346b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:07:02 +0000 Subject: [PATCH 099/125] Bump Microsoft.Data.Sqlite from 9.0.9 to 9.0.10 --- updated-dependencies: - dependency-name: Microsoft.Data.Sqlite dependency-version: 9.0.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .../Flow.Launcher.Plugin.BrowserBookmark.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8392a0dbe..8f0dfb4f5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -105,7 +105,7 @@ - + From 0131b92c7a5801214b7fc1c9293f27621334363a Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 22:47:58 -0300 Subject: [PATCH 100/125] The HistoryStyle property is used to distinguish history items --- Flow.Launcher/Storage/LastOpenedHistoryItem.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs index 47647066c..1cf93b1ff 100644 --- a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -1,4 +1,5 @@ using System; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; @@ -10,22 +11,7 @@ public class LastOpenedHistoryItem public string PluginID { get; set; } = string.Empty; public string Query { get; set; } = string.Empty; public string RecordKey { get; set; } = string.Empty; + public HistoryStyle HistoryStyle { get; set; } public DateTime ExecutedDateTime { get; set; } - public bool Equals(Result r) - { - if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey)) - { - return Title == r.Title - && SubTitle == r.SubTitle - && PluginID == r.PluginID - && Query == r.OriginQuery.RawQuery; - } - else - { - return RecordKey == r.RecordKey - && PluginID == r.PluginID - && Query == r.OriginQuery.RawQuery; - } - } } From ced824d791c7cd2e78a26aa5695d328e4463bfc4 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 22:49:03 -0300 Subject: [PATCH 101/125] is equals --- Flow.Launcher/Helper/ResultHelper.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index f99ba0377..a0a7b0793 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Core.Plugin; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Storage; @@ -41,4 +42,18 @@ public static class ResultHelper return null; } } + + public static bool IsEquals(this Result result, LastOpenedHistoryItem item, HistoryStyle style) + { + bool keyMatches = string.IsNullOrEmpty(result.RecordKey) + ? item.Title == result.Title + : item.RecordKey == result.RecordKey; + + bool queryMatches = style != HistoryStyle.Query || item.Query == result.OriginQuery.RawQuery; + + return keyMatches + && queryMatches + && item.PluginID == result.PluginID + && item.HistoryStyle == style; + } } From d5a2695766b9121c0e3debdd37a7ac2d8f511cb3 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 22:50:29 -0300 Subject: [PATCH 102/125] Update the history item if it equals the last added item or already exists in the list. --- Flow.Launcher/Storage/QueryHistory.cs | 55 ++++++++++++++++++--------- 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index 7d264d09f..7be099115 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage @@ -16,8 +19,11 @@ namespace Flow.Launcher.Storage [JsonInclude] public List LastOpenedHistoryItems { get; private set; } = []; + private readonly Settings _settings = Ioc.Default.GetRequiredService(); + private readonly int _maxHistory = 300; + public void PopulateHistoryFromLegacyHistory() { if (Items.Count == 0) return; @@ -25,7 +31,7 @@ namespace Flow.Launcher.Storage foreach (var item in Items) { LastOpenedHistoryItems.Add(new LastOpenedHistoryItem - { + { Query = item.Query, ExecutedDateTime = item.ExecutedDateTime }); @@ -34,33 +40,48 @@ namespace Flow.Launcher.Storage } public void Add(Result result) - { + { if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; + if (string.IsNullOrEmpty(result.PluginID)) return; + var style = _settings.HistoryStyle; // Maintain the max history limit - if (LastOpenedHistoryItems.Count > _maxHistory) + if (LastOpenedHistoryItems.Count > _maxHistory) { LastOpenedHistoryItems.RemoveAt(0); } // If the last item is the same as the current result, just update the timestamp - if (LastOpenedHistoryItems.Count > 0 && - LastOpenedHistoryItems.Last().Equals(result)) + if (LastOpenedHistoryItems.Count > 0) { - LastOpenedHistoryItems.Last().ExecutedDateTime = DateTime.Now; - } - else - { - LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + var last = LastOpenedHistoryItems.Last(); + if (result.IsEquals(last, style)) { - Title = result.Title, - SubTitle = result.SubTitle, - PluginID = result.PluginID, - Query = result.OriginQuery.RawQuery, - RecordKey = result.RecordKey, - ExecutedDateTime = DateTime.Now - }); + last.ExecutedDateTime = DateTime.Now; + return; + } + + var existItem = LastOpenedHistoryItems.FirstOrDefault(x => result.IsEquals(x, style)); + + if (existItem != null) + { + existItem.ExecutedDateTime = DateTime.Now; + return; + } } + + LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + { + Title = result.Title, + SubTitle = result.SubTitle, + PluginID = result.PluginID, + Query = result.OriginQuery.RawQuery, + RecordKey = result.RecordKey, + ExecutedDateTime = DateTime.Now, + HistoryStyle = style + }); } + + } } From 9f652c33b4900a7a7738a483b9de2bf155701b52 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 22:51:09 -0300 Subject: [PATCH 103/125] History items filtered based on HistoryStyle. --- Flow.Launcher/ViewModel/MainViewModel.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 8fd6de6f5..22476a79c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1318,9 +1318,10 @@ namespace Flow.Launcher.ViewModel private List GetHistoryItems(IEnumerable historyItems) { var results = new List(); + var historyItemsFiltered = historyItems.Where(x => x.HistoryStyle == Settings.HistoryStyle).ToList(); if (Settings.HistoryStyle == HistoryStyle.Query) { - foreach (var h in historyItems) + foreach (var h in historyItemsFiltered) { var result = new Result { @@ -1341,7 +1342,7 @@ namespace Flow.Launcher.ViewModel } else { - foreach (var h in historyItems) + foreach (var h in historyItemsFiltered) { var result = new Result { @@ -1364,15 +1365,14 @@ namespace Flow.Launcher.ViewModel { await reflectResult.AsyncAction(c); } + return false; } - else - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(h.Query); - return false; - } - }, + + App.API.BackToQueryResults(); + App.API.ChangeQuery(h.Query); + return false; + }, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; results.Add(result); From 8e96d1a461ac229be6f9fbdd2798282c521bfb10 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 22:51:28 -0300 Subject: [PATCH 104/125] HistoryStyle enum values --- Flow.Launcher.Infrastructure/UserSettings/Settings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 6adefdb6a..da712e3fc 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -716,9 +716,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings public enum HistoryStyle { [EnumLocalizeKey(nameof(Localize.queryHistory))] - Query, + Query = 1, [EnumLocalizeKey(nameof(Localize.executedHistory))] - LastOpened + LastOpened = 2 } } From f31abe83b0c2ee41cbb5922b5826993bc3b6c1bf Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 23:12:25 -0300 Subject: [PATCH 105/125] PopulateHistoryFromLegacyHistory with HistoryStyle --- Flow.Launcher/Storage/QueryHistory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index 7be099115..5d19aed38 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -33,7 +33,8 @@ namespace Flow.Launcher.Storage LastOpenedHistoryItems.Add(new LastOpenedHistoryItem { Query = item.Query, - ExecutedDateTime = item.ExecutedDateTime + ExecutedDateTime = item.ExecutedDateTime, + HistoryStyle = HistoryStyle.Query }); } Items.Clear(); From 764c674350a6696bd9d5b646d987504f68f8097d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 23:15:14 -0300 Subject: [PATCH 106/125] code quality --- Flow.Launcher/Helper/ResultHelper.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index a0a7b0793..d4732055b 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -45,11 +45,12 @@ public static class ResultHelper public static bool IsEquals(this Result result, LastOpenedHistoryItem item, HistoryStyle style) { - bool keyMatches = string.IsNullOrEmpty(result.RecordKey) - ? item.Title == result.Title - : item.RecordKey == result.RecordKey; + bool keyMatches = string.IsNullOrEmpty(result.RecordKey) && string.IsNullOrEmpty(item.RecordKey) + ? item.Title == result.Title + : !string.IsNullOrEmpty(result.RecordKey) && !string.IsNullOrEmpty(item.RecordKey) && item.RecordKey == result.RecordKey; + + bool queryMatches = style != HistoryStyle.Query || (result.OriginQuery != null && item.Query == result.OriginQuery.RawQuery); - bool queryMatches = style != HistoryStyle.Query || item.Query == result.OriginQuery.RawQuery; return keyMatches && queryMatches From a8d3cdf62cec6e81e5862bbc9f60c6974d99ae83 Mon Sep 17 00:00:00 2001 From: 01Dri Date: Tue, 14 Oct 2025 23:26:32 -0300 Subject: [PATCH 107/125] SubTitle equals --- Flow.Launcher/Helper/ResultHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index d4732055b..f2ba66cbf 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -54,6 +54,7 @@ public static class ResultHelper return keyMatches && queryMatches + && item.SubTitle == result.SubTitle && item.PluginID == result.PluginID && item.HistoryStyle == style; } From 9ca7c8431bfb7e196ce8ff672ad72fd252b09c1d Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 15 Oct 2025 00:34:16 -0300 Subject: [PATCH 108/125] Revert modifications and returning actions --- .../UserSettings/Settings.cs | 4 +- Flow.Launcher/Helper/ResultHelper.cs | 17 ------ .../Storage/LastOpenedHistoryItem.cs | 18 +++++- Flow.Launcher/Storage/QueryHistory.cs | 58 ++++++------------- Flow.Launcher/ViewModel/MainViewModel.cs | 18 +++--- 5 files changed, 45 insertions(+), 70 deletions(-) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index da712e3fc..6adefdb6a 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -716,9 +716,9 @@ namespace Flow.Launcher.Infrastructure.UserSettings public enum HistoryStyle { [EnumLocalizeKey(nameof(Localize.queryHistory))] - Query = 1, + Query, [EnumLocalizeKey(nameof(Localize.executedHistory))] - LastOpened = 2 + LastOpened } } diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index f2ba66cbf..f99ba0377 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.Storage; @@ -42,20 +41,4 @@ public static class ResultHelper return null; } } - - public static bool IsEquals(this Result result, LastOpenedHistoryItem item, HistoryStyle style) - { - bool keyMatches = string.IsNullOrEmpty(result.RecordKey) && string.IsNullOrEmpty(item.RecordKey) - ? item.Title == result.Title - : !string.IsNullOrEmpty(result.RecordKey) && !string.IsNullOrEmpty(item.RecordKey) && item.RecordKey == result.RecordKey; - - bool queryMatches = style != HistoryStyle.Query || (result.OriginQuery != null && item.Query == result.OriginQuery.RawQuery); - - - return keyMatches - && queryMatches - && item.SubTitle == result.SubTitle - && item.PluginID == result.PluginID - && item.HistoryStyle == style; - } } diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs index 1cf93b1ff..47647066c 100644 --- a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -1,5 +1,4 @@ using System; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage; @@ -11,7 +10,22 @@ public class LastOpenedHistoryItem public string PluginID { get; set; } = string.Empty; public string Query { get; set; } = string.Empty; public string RecordKey { get; set; } = string.Empty; - public HistoryStyle HistoryStyle { get; set; } public DateTime ExecutedDateTime { get; set; } + public bool Equals(Result r) + { + if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey)) + { + return Title == r.Title + && SubTitle == r.SubTitle + && PluginID == r.PluginID + && Query == r.OriginQuery.RawQuery; + } + else + { + return RecordKey == r.RecordKey + && PluginID == r.PluginID + && Query == r.OriginQuery.RawQuery; + } + } } diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index 5d19aed38..7d264d09f 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -2,9 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Helper; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; namespace Flow.Launcher.Storage @@ -19,11 +16,8 @@ namespace Flow.Launcher.Storage [JsonInclude] public List LastOpenedHistoryItems { get; private set; } = []; - private readonly Settings _settings = Ioc.Default.GetRequiredService(); - private readonly int _maxHistory = 300; - public void PopulateHistoryFromLegacyHistory() { if (Items.Count == 0) return; @@ -31,58 +25,42 @@ namespace Flow.Launcher.Storage foreach (var item in Items) { LastOpenedHistoryItems.Add(new LastOpenedHistoryItem - { + { Query = item.Query, - ExecutedDateTime = item.ExecutedDateTime, - HistoryStyle = HistoryStyle.Query + ExecutedDateTime = item.ExecutedDateTime }); } Items.Clear(); } public void Add(Result result) - { + { if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; - if (string.IsNullOrEmpty(result.PluginID)) return; - var style = _settings.HistoryStyle; // Maintain the max history limit - if (LastOpenedHistoryItems.Count > _maxHistory) + if (LastOpenedHistoryItems.Count > _maxHistory) { LastOpenedHistoryItems.RemoveAt(0); } // If the last item is the same as the current result, just update the timestamp - if (LastOpenedHistoryItems.Count > 0) + if (LastOpenedHistoryItems.Count > 0 && + LastOpenedHistoryItems.Last().Equals(result)) { - var last = LastOpenedHistoryItems.Last(); - if (result.IsEquals(last, style)) - { - last.ExecutedDateTime = DateTime.Now; - return; - } - - var existItem = LastOpenedHistoryItems.FirstOrDefault(x => result.IsEquals(x, style)); - - if (existItem != null) - { - existItem.ExecutedDateTime = DateTime.Now; - return; - } + LastOpenedHistoryItems.Last().ExecutedDateTime = DateTime.Now; } - - LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + else { - Title = result.Title, - SubTitle = result.SubTitle, - PluginID = result.PluginID, - Query = result.OriginQuery.RawQuery, - RecordKey = result.RecordKey, - ExecutedDateTime = DateTime.Now, - HistoryStyle = style - }); + LastOpenedHistoryItems.Add(new LastOpenedHistoryItem + { + Title = result.Title, + SubTitle = result.SubTitle, + PluginID = result.PluginID, + Query = result.OriginQuery.RawQuery, + RecordKey = result.RecordKey, + ExecutedDateTime = DateTime.Now + }); + } } - - } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 22476a79c..8fd6de6f5 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1318,10 +1318,9 @@ namespace Flow.Launcher.ViewModel private List GetHistoryItems(IEnumerable historyItems) { var results = new List(); - var historyItemsFiltered = historyItems.Where(x => x.HistoryStyle == Settings.HistoryStyle).ToList(); if (Settings.HistoryStyle == HistoryStyle.Query) { - foreach (var h in historyItemsFiltered) + foreach (var h in historyItems) { var result = new Result { @@ -1342,7 +1341,7 @@ namespace Flow.Launcher.ViewModel } else { - foreach (var h in historyItemsFiltered) + foreach (var h in historyItems) { var result = new Result { @@ -1365,14 +1364,15 @@ namespace Flow.Launcher.ViewModel { await reflectResult.AsyncAction(c); } - return false; } - - App.API.BackToQueryResults(); - App.API.ChangeQuery(h.Query); - return false; - }, + else + { + App.API.BackToQueryResults(); + App.API.ChangeQuery(h.Query); + return false; + } + }, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; results.Add(result); From 10d353defe6dfb42ed20f04db78f5091c41218cf Mon Sep 17 00:00:00 2001 From: 01Dri Date: Wed, 15 Oct 2025 00:34:38 -0300 Subject: [PATCH 109/125] Returning actions --- Flow.Launcher/Storage/QueryHistory.cs | 1 + Flow.Launcher/ViewModel/MainViewModel.cs | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index 7d264d09f..8284f7eea 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -36,6 +36,7 @@ namespace Flow.Launcher.Storage public void Add(Result result) { if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return; + if (string.IsNullOrEmpty(result.PluginID)) return; // Maintain the max history limit if (LastOpenedHistoryItems.Count > _maxHistory) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 8fd6de6f5..d9bcf0a33 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1358,20 +1358,17 @@ namespace Flow.Launcher.ViewModel { if (reflectResult.Action != null) { - reflectResult.Action(c); + return reflectResult.Action(c); } - else if (reflectResult.AsyncAction != null) + if (reflectResult.AsyncAction != null) { - await reflectResult.AsyncAction(c); + return await reflectResult.AsyncAction(c); } return false; } - else - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(h.Query); - return false; - } + App.API.BackToQueryResults(); + App.API.ChangeQuery(h.Query); + return false; }, Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C") }; From 4b6ee4ea98dc73d6f8b760483d60e23a2ddbc3e8 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:23:00 +0800 Subject: [PATCH 110/125] Improve code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d9bcf0a33..4df87208c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -422,7 +422,6 @@ namespace Flow.Launcher.ViewModel _ = Task.Run(() => DialogJump.JumpToPathAsync(DialogWindowHandle, dialogJumpResult.DialogJumpPath)); } } - return; } @@ -1358,14 +1357,20 @@ namespace Flow.Launcher.ViewModel { if (reflectResult.Action != null) { + // Since some actions may need to hide the Flow window to execute + // So let us populate the results of them return reflectResult.Action(c); } if (reflectResult.AsyncAction != null) { - return await reflectResult.AsyncAction(c); + // Since some actions may need to hide the Flow window to execute + // So let us populate the results of them + return await reflectResult.AsyncAction(c); } return false; } + + // If we cannot get the result, fallback to re-query App.API.BackToQueryResults(); App.API.ChangeQuery(h.Query); return false; From 1298b76b7781c0a5b957c78c17d08e4dd0552767 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:23:52 +0800 Subject: [PATCH 111/125] Track user-selected results for ranking purposes Added logic to record user-selected results in `_userSelectedRecord` before executing both synchronous and asynchronous actions. This enables tracking of user interactions for result ranking or analytics. Comments were added to clarify the purpose of the new logic. --- Flow.Launcher/ViewModel/MainViewModel.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 4df87208c..14f6dda39 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1357,12 +1357,18 @@ namespace Flow.Launcher.ViewModel { if (reflectResult.Action != null) { + // Record the user selected record for result ranking + _userSelectedRecord.Add(reflectResult); + // Since some actions may need to hide the Flow window to execute // So let us populate the results of them return reflectResult.Action(c); } if (reflectResult.AsyncAction != null) { + // Record the user selected record for result ranking + _userSelectedRecord.Add(reflectResult); + // Since some actions may need to hide the Flow window to execute // So let us populate the results of them return await reflectResult.AsyncAction(c); From c73689fbc3701812d0e05a47c4add7fad959a044 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:24:36 +0800 Subject: [PATCH 112/125] Fix spelling --- Directory.Build.props | 2 +- Flow.Launcher/Flow.Launcher.csproj | 2 +- Flow.Launcher/MainWindow.xaml.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a5545af12..b8c1d13ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ true - + false \ No newline at end of file diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 8c7670426..576bf6f2f 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -185,7 +185,7 @@ - + diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 88d6b6606..530ca8488 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -860,7 +860,7 @@ namespace Flow.Launcher public void UpdatePosition() { - // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 + // Initialize call twice to workaround multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 if (_viewModel.IsDialogJumpWindowUnderDialog()) { InitializeDialogJumpPosition(); @@ -884,7 +884,7 @@ namespace Flow.Launcher private void InitializePosition() { - // Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 + // Initialize call twice to workaround multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910 InitializePositionInner(); InitializePositionInner(); return; From 629c2eb4c65b752a5796f5268f138468e514656d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:26:21 +0800 Subject: [PATCH 113/125] Record user-selected results for ranking Moved `_userSelectedRecord.Add(result)` outside the `if (queryResultsSelected)` block to ensure all user-selected results are recorded, regardless of their source (query results, context menu, or history). Added a comment to clarify that only query results are added to history. --- Flow.Launcher/ViewModel/MainViewModel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 14f6dda39..4c5c0229c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -532,9 +532,12 @@ namespace Flow.Launcher.ViewModel Hide(); } } + + // Record user selected result for result ranking + _userSelectedRecord.Add(result); + // Add item to histroy only if it is from results but not context menu or history if (queryResultsSelected) { - _userSelectedRecord.Add(result); _history.Add(result); lastHistoryIndex = 1; } From b784a14aee3c4004b410444d4576809eaabc324d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:28:54 +0800 Subject: [PATCH 114/125] Update tooltip text for `historyStyleTooltip` The tooltip text for the key `historyStyleTooltip` was updated to improve capitalization and consistency. "History" and "Home Page" were capitalized to align with the formatting of other keys in the file. --- Flow.Launcher/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index dd35bcc1d..2ced29353 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -167,7 +167,7 @@ Show History Results in Home Page Maximum History Results Shown in Home Page History Style - Choose the type of history to show in the history and home page + Choose the type of history to show in the History and Home Page Query history Last opened history This can only be edited if plugin supports Home feature and Home Page is enabled. From 83cab764a125b328367e84d9df36191f85fe57ba Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Wed, 15 Oct 2025 12:30:59 +0800 Subject: [PATCH 115/125] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 4c5c0229c..d0c59a7f2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -535,7 +535,7 @@ namespace Flow.Launcher.ViewModel // Record user selected result for result ranking _userSelectedRecord.Add(result); - // Add item to histroy only if it is from results but not context menu or history + // Add item to history only if it is from results but not context menu or history if (queryResultsSelected) { _history.Add(result); From df4f08b071b82866b99430327843b20a5ecd64f9 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:35:53 +0800 Subject: [PATCH 116/125] Add exception logging to ResultHelper catch block Updated the `catch` block in the `ResultHelper` class to explicitly catch `System.Exception` and log the error using `App.API.LogException`. The log includes the class name, a failure message for querying results, and the exception details. This improves error visibility and debugging. --- Flow.Launcher/Helper/ResultHelper.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index f99ba0377..389b06b4f 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -36,8 +36,9 @@ public static class ResultHelper freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle); } } - catch + catch (System.Exception e) { + App.API.LogException(nameof(ResultHelper), $"Failed to query results for {plugin.Metadata.Name}", e); return null; } } From b13c29ab91aee6f2a8674ee232811fa425bf2ded Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 12:40:00 +0800 Subject: [PATCH 117/125] Record user selection after successful execution --- Flow.Launcher/ViewModel/MainViewModel.cs | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d0c59a7f2..706be8bb8 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1358,25 +1358,12 @@ namespace Flow.Launcher.ViewModel var reflectResult = await ResultHelper.PopulateResultsAsync(h); if (reflectResult != null) { - if (reflectResult.Action != null) - { - // Record the user selected record for result ranking - _userSelectedRecord.Add(reflectResult); + // Record the user selected record for result ranking + _userSelectedRecord.Add(reflectResult); - // Since some actions may need to hide the Flow window to execute - // So let us populate the results of them - return reflectResult.Action(c); - } - if (reflectResult.AsyncAction != null) - { - // Record the user selected record for result ranking - _userSelectedRecord.Add(reflectResult); - - // Since some actions may need to hide the Flow window to execute - // So let us populate the results of them - return await reflectResult.AsyncAction(c); - } - return false; + // Since some actions may need to hide the Flow window to execute + // So let us populate the results of them + return await reflectResult.ExecuteAsync(c); } // If we cannot get the result, fallback to re-query From c00d804073b35f068f94ff51253a8475c919575d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 15 Oct 2025 13:07:43 +0800 Subject: [PATCH 118/125] Resolve conflicts --- Flow.Launcher/Helper/ResultHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index 389b06b4f..5f9a69f28 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -20,7 +20,7 @@ public static class ResultHelper { var plugin = PluginManager.GetPluginForId(pluginId); if (plugin == null) return null; - var query = QueryBuilder.Build(rawQuery, PluginManager.NonGlobalPlugins); + var query = QueryBuilder.Build(rawQuery, PluginManager.GetNonGlobalPlugins()); if (query == null) return null; try { From 3690042d2c5438854a82f6fe87e94f515712796e Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Wed, 15 Oct 2025 17:12:51 +0800 Subject: [PATCH 119/125] Fix DialogJump UriFormatException when navigating to root directories (#4052) --- .../DialogJump/DialogJump.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs index 9035a541d..3941bc114 100644 --- a/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs +++ b/Flow.Launcher.Infrastructure/DialogJump/DialogJump.cs @@ -828,9 +828,25 @@ namespace Flow.Launcher.Infrastructure.DialogJump return true; } // file: URI paths - var localPath = path.StartsWith("file:", StringComparison.OrdinalIgnoreCase) - ? new Uri(path).LocalPath - : path; + string localPath; + if (path.StartsWith("file:", StringComparison.OrdinalIgnoreCase)) + { + // Try to create a URI from the path + if (Uri.TryCreate(path, UriKind.Absolute, out var uri)) + { + localPath = uri.LocalPath; + } + else + { + // If URI creation fails, treat it as a regular path + // by removing the "file:" prefix + localPath = path.Substring(5); + } + } + else + { + localPath = path; + } // Is folder? var isFolder = Directory.Exists(localPath); // Is file? From 386737acac58cdaa133c92c33bae19d5e97c105c Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 16 Oct 2025 20:20:51 +1100 Subject: [PATCH 120/125] update method parameter style --- Flow.Launcher.Core/Plugin/PluginManager.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 299d32106..a91d94b90 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -54,7 +54,7 @@ namespace Flow.Launcher.Core.Plugin /// public static void Save() { - foreach (var pluginPair in GetAllInitializedPlugins(false)) + foreach (var pluginPair in GetAllInitializedPlugins(includeFailed: false)) { var savable = pluginPair.Plugin as ISavable; try @@ -74,7 +74,7 @@ namespace Flow.Launcher.Core.Plugin public static async ValueTask DisposePluginsAsync() { // Still call dispose for all plugins even if initialization failed, so that we can clean up resources - foreach (var pluginPair in GetAllInitializedPlugins(true)) + foreach (var pluginPair in GetAllInitializedPlugins(includeFailed: true)) { await DisposePluginAsync(pluginPair); } @@ -102,7 +102,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task ReloadDataAsync() { - await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(includeFailed: false).Select(plugin => plugin.Plugin switch { IReloadable p => Task.Run(p.ReloadData), IAsyncReloadable p => p.ReloadDataAsync(), @@ -116,7 +116,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(includeFailed: false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -125,7 +125,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task CloseExternalPreviewAsync() { - await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(includeFailed: false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.ClosePreviewAsync(), _ => Task.CompletedTask, @@ -134,7 +134,7 @@ namespace Flow.Launcher.Core.Plugin public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) { - await Task.WhenAll([.. GetAllInitializedPlugins(false).Select(plugin => plugin.Plugin switch + await Task.WhenAll([.. GetAllInitializedPlugins(includeFailed: false).Select(plugin => plugin.Plugin switch { IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), _ => Task.CompletedTask, @@ -824,7 +824,7 @@ namespace Flow.Launcher.Core.Plugin return true; // If version is not valid, we assume it is lesser than any existing version // Get all plugins even if initialization failed so that we can check if the plugin with the same ID exists - return GetAllInitializedPlugins(true).Any(x => x.Metadata.ID == newMetadata.ID + return GetAllInitializedPlugins(includeFailed: true).Any(x => x.Metadata.ID == newMetadata.ID && Version.TryParse(x.Metadata.Version, out var version) && newVersion <= version); } @@ -966,7 +966,7 @@ namespace Flow.Launcher.Core.Plugin // If we want to remove plugin from AllPlugins, // we need to dispose them so that they can release file handles // which can help FL to delete the plugin settings & cache folders successfully - var pluginPairs = GetAllInitializedPlugins(true).Where(p => p.Metadata.ID == plugin.ID).ToList(); + var pluginPairs = GetAllInitializedPlugins(includeFailed: true).Where(p => p.Metadata.ID == plugin.ID).ToList(); foreach (var pluginPair in pluginPairs) { await DisposePluginAsync(pluginPair); From bd880d304ed8c6a6e75703c5c8df7f45e8fff09a Mon Sep 17 00:00:00 2001 From: Jeremy Wu Date: Thu, 16 Oct 2025 20:46:35 +1100 Subject: [PATCH 121/125] update wording --- Flow.Launcher/App.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index d2e5838d3..b45bbc549 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -227,8 +227,8 @@ namespace Flow.Launcher Current.MainWindow = _mainWindow; Current.MainWindow.Title = Constant.FlowLauncher; - // Initialize quick jump before hotkey mapper since hotkey mapper will register quick jump hotkey - // Initialize quick jump after main window is created so that it can access main window handle + // Initialize Dialog Jump before hotkey mapper since hotkey mapper will register its hotkey + // Initialize Dialog Jump after main window is created so that it can access main window handle DialogJump.InitializeDialogJump(); DialogJump.SetupDialogJump(_settings.EnableDialogJump); From 68454a8a6ea7a832e827981b5606eebcf7a32402 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 18:43:19 +0800 Subject: [PATCH 122/125] Change log level for plugin constructor timing message Updated the log level for the plugin constructor cost message from `LogInfo` to `LogDebug` to reduce verbosity in production logs and make this information available primarily during debugging sessions. --- Flow.Launcher.Core/Plugin/PluginsLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 6cad20b16..119dd83ba 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -108,7 +108,7 @@ namespace Flow.Launcher.Core.Plugin }); metadata.InitTime += milliseconds; - PublicApi.Instance.LogInfo(ClassName, $"Constructor cost for <{metadata.Name}> is <{metadata.InitTime}ms>"); + PublicApi.Instance.LogDebug(ClassName, $"Constructor cost for <{metadata.Name}> is <{metadata.InitTime}ms>"); } if (erroredPlugins.Count > 0) From fbeaafa8d384229d7ae6171a15c42e9e77a089d4 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Thu, 16 Oct 2025 18:47:15 +0800 Subject: [PATCH 123/125] Remove `Score = -100` from result objects in PluginManager Removed the `Score = -100` property from multiple result objects in `PluginManager.cs` to simplify the code and improve clarity. Adjusted the formatting and structure to ensure proper syntax and maintain code consistency. This includes changes to result objects that handle re-querying and exception handling. --- Flow.Launcher.Core/Plugin/PluginManager.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index a91d94b90..d3a8003c1 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -411,8 +411,7 @@ namespace Flow.Launcher.Core.Plugin { PublicApi.Instance.ReQuery(); return false; - }, - Score = -100 + } }; results.Add(r); return results; @@ -450,8 +449,7 @@ namespace Flow.Launcher.Core.Plugin ActionKeywordAssigned = query.ActionKeyword, PluginID = metadata.ID, OriginQuery = query, - Action = _ => { throw new FlowPluginException(metadata, e);}, - Score = -100 + Action = _ => { throw new FlowPluginException(metadata, e);} }; results.Add(r); } @@ -479,8 +477,7 @@ namespace Flow.Launcher.Core.Plugin { PublicApi.Instance.ReQuery(); return false; - }, - Score = -100 + } }; results.Add(r); return results; From c9e3c628e8e2a7064f37572d06ac927a25fb43bd Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 16 Oct 2025 18:48:07 +0800 Subject: [PATCH 124/125] Improve code comments Co-authored-by: Jeremy Wu --- Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index f156979c6..93844159f 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -174,7 +174,7 @@ namespace Flow.Launcher.Plugin /// Get all loaded plugins /// /// - /// Part of plugins may not be initialized yet + /// Will also return any plugins not fully initialized yet /// /// List GetAllPlugins(); From fb84cb03f33e8b841001489b5ed55c9e13bdab6e Mon Sep 17 00:00:00 2001 From: Jack Ye Date: Thu, 16 Oct 2025 18:48:17 +0800 Subject: [PATCH 125/125] Improve code comments Co-authored-by: Jeremy Wu --- Flow.Launcher/Languages/en.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 50e7d7d5e..548441416 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -66,7 +66,7 @@ Position Reset Reset search window position Type here to search - {0}: This plugin is still initializing! + {0}: This plugin is still initializing... Select this result to requery {0}: Failed to respond! Select this result for more info