diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 72303c8b7..b0f896214 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -25,6 +25,7 @@ namespace Flow.Launcher.Core.Plugin private static readonly string ClassName = nameof(PluginManager); private static IEnumerable _contextMenuPlugins; + private static IEnumerable _homePlugins; public static List AllPlugins { get; private set; } public static readonly HashSet GlobalPlugins = new(); @@ -227,6 +228,8 @@ namespace Flow.Launcher.Core.Plugin await Task.WhenAll(InitTasks); _contextMenuPlugins = GetPluginsForInterface(); + _homePlugins = GetPluginsForInterface(); + foreach (var plugin in AllPlugins) { // set distinct on each plugin's action keywords helps only firing global(*) and action keywords once where a plugin @@ -274,6 +277,14 @@ namespace Flow.Launcher.Core.Plugin }; } + public static ICollection ValidPluginsForHomeQuery(Query query) + { + if (query is not null) + return Array.Empty(); + + return _homePlugins.ToList(); + } + public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) { var results = new List(); @@ -318,6 +329,36 @@ namespace Flow.Launcher.Core.Plugin return results; } + public static async Task> QueryHomeForPluginAsync(PluginPair pair, CancellationToken token) + { + var results = new List(); + var metadata = pair.Metadata; + + try + { + var milliseconds = await API.StopwatchLogDebugAsync(ClassName, $"Cost for {metadata.Name}", + async () => results = await ((IAsyncHomeQuery)pair.Plugin).HomeQueryAsync(token).ConfigureAwait(false)); + + token.ThrowIfCancellationRequested(); + if (results == null) + return null; + UpdatePluginMetadata(results, metadata); + + token.ThrowIfCancellationRequested(); + } + catch (OperationCanceledException) + { + // null will be fine since the results will only be added into queue if the token hasn't been cancelled + return null; + } + catch (Exception e) + { + API.LogException(ClassName, $"Failed to query home for plugin: {metadata.Name}", e); + return null; + } + return results; + } + public static void UpdatePluginMetadata(IReadOnlyList results, PluginMetadata metadata, Query query) { foreach (var r in results) @@ -333,6 +374,16 @@ namespace Flow.Launcher.Core.Plugin } } + private static void UpdatePluginMetadata(IReadOnlyList results, PluginMetadata metadata) + { + foreach (var r in results) + { + r.PluginDirectory = metadata.PluginDirectory; + r.PluginID = metadata.ID; + r.OriginQuery = null; + } + } + /// /// get specified plugin, return null if not found /// diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index b7a1d1f63..2b5fc6bc0 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -158,6 +158,11 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } } + + public bool ShowHomeQuery { get; set; } = true; + public bool ShowHistoryRecordsForHomeQuery { get; set; } = false; + public int HistoryRecordsCountForHomeQuery { get; set; } = 5; + public int CustomExplorerIndex { get; set; } = 0; [JsonIgnore] diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index f0fcd48ff..060c03317 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -174,6 +174,9 @@ namespace Flow.Launcher.Plugin /// /// Query information associated with the result /// + /// + /// If the query is for home query, this will be null + /// internal Query OriginQuery { get; set; } /// diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2f1ed0f51..b5afe4b7e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1198,21 +1198,31 @@ namespace Flow.Launcher.ViewModel _updateSource?.Cancel(); var query = await ConstructQueryAsync(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts); + var homeQuery = query == null; + ICollection plugins = Array.Empty(); if (query == null) // shortcut expanded { - // Hide and clear results again because running query may show and add some results - Results.Visibility = Visibility.Collapsed; - Results.Clear(); + if (Settings.ShowHomeQuery) + { + plugins = PluginManager.ValidPluginsForHomeQuery(query); + } + + if (plugins.Count == 0) + { + // Hide and clear results again because running query may show and add some results + Results.Visibility = Visibility.Collapsed; + Results.Clear(); - // Reset plugin icon - PluginIconPath = null; - PluginIconSource = null; - SearchIconVisibility = Visibility.Visible; + // Reset plugin icon + PluginIconPath = null; + PluginIconSource = null; + SearchIconVisibility = Visibility.Visible; - // Hide progress bar again because running query may set this to visible - ProgressBarVisibility = Visibility.Hidden; - return; + // Hide progress bar again because running query may set this to visible + ProgressBarVisibility = Visibility.Hidden; + return; + } } _updateSource = new CancellationTokenSource(); @@ -1226,27 +1236,37 @@ namespace Flow.Launcher.ViewModel if (_updateSource.Token.IsCancellationRequested) return; // Update the query's IsReQuery property to true if this is a re-query - query.IsReQuery = isReQuery; + if (!homeQuery) query.IsReQuery = isReQuery; // handle the exclusiveness of plugin using action keyword RemoveOldQueryResults(query); _lastQuery = query; - var plugins = PluginManager.ValidPluginsForQuery(query); - - if (plugins.Count == 1) - { - PluginIconPath = plugins.Single().Metadata.IcoPath; - PluginIconSource = await App.API.LoadImageAsync(PluginIconPath); - SearchIconVisibility = Visibility.Hidden; - } - else + if (homeQuery) { + // Do not show plugin icon if this is a home query PluginIconPath = null; PluginIconSource = null; SearchIconVisibility = Visibility.Visible; } + else + { + plugins = PluginManager.ValidPluginsForQuery(query); + + if (plugins.Count == 1) + { + PluginIconPath = plugins.Single().Metadata.IcoPath; + PluginIconSource = await App.API.LoadImageAsync(PluginIconPath); + SearchIconVisibility = Visibility.Hidden; + } + else + { + PluginIconPath = null; + PluginIconSource = null; + SearchIconVisibility = Visibility.Visible; + } + } // Do not wait for performance improvement /*if (string.IsNullOrEmpty(query.ActionKeyword)) @@ -1303,7 +1323,7 @@ namespace Flow.Launcher.ViewModel // Local function async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) { - if (searchDelay) + if (searchDelay && !homeQuery) // Do not delay for home query { var searchDelayTime = plugin.Metadata.SearchDelayTime ?? Settings.SearchDelayTime; @@ -1316,7 +1336,9 @@ namespace Flow.Launcher.ViewModel // Task.Yield will force it to run in ThreadPool await Task.Yield(); - var results = await PluginManager.QueryForPluginAsync(plugin, query, token); + var results = homeQuery ? + await PluginManager.QueryHomeForPluginAsync(plugin, token) : + await PluginManager.QueryForPluginAsync(plugin, query, token); if (token.IsCancellationRequested) return; @@ -1616,7 +1638,7 @@ namespace Flow.Launcher.ViewModel break; case LastQueryMode.ActionKeywordPreserved: case LastQueryMode.ActionKeywordSelected: - var newQuery = _lastQuery.ActionKeyword; + var newQuery = _lastQuery?.ActionKeyword; if (!string.IsNullOrEmpty(newQuery)) newQuery += " ";