diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 72303c8b754..a3e80a73f74 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -25,6 +25,7 @@ public static class PluginManager 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(); @@ -220,6 +221,7 @@ public static async Task InitializePluginsAsync() { API.LogException(ClassName, $"Fail to Init plugin: {pair.Metadata.Name}", e); pair.Metadata.Disabled = true; + pair.Metadata.HomeDisabled = true; failedPlugins.Enqueue(pair); } })); @@ -227,6 +229,8 @@ public static async Task InitializePluginsAsync() 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 +278,11 @@ public static ICollection ValidPluginsForQuery(Query query) }; } + public static ICollection ValidPluginsForHomeQuery() + { + return _homePlugins.ToList(); + } + public static async Task> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token) { var results = new List(); @@ -318,6 +327,36 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer return results; } + public static async Task> QueryHomeForPluginAsync(PluginPair pair, Query query, 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, query); + + 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) @@ -378,6 +417,11 @@ public static List GetContextMenusForPlugin(Result result) return results; } + public static bool IsHomePlugin(string id) + { + return _homePlugins.Any(p => p.Metadata.ID == id); + } + public static bool ActionKeywordRegistered(string actionKeyword) { // this method is only checking for action keywords (defined as not '*') registration diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index 0ef3f30f5e1..fae821736fb 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -8,10 +8,23 @@ public static class QueryBuilder { public static Query Build(string text, Dictionary nonGlobalPlugins) { + // home query + if (string.IsNullOrEmpty(text)) + { + return new Query() + { + Search = string.Empty, + RawQuery = string.Empty, + SearchTerms = Array.Empty(), + ActionKeyword = string.Empty + }; + } + // replace multiple white spaces with one white space var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries); if (terms.Length == 0) - { // nothing was typed + { + // nothing was typed return null; } @@ -21,13 +34,15 @@ public static Query Build(string text, Dictionary nonGlobalP string[] searchTerms; if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) - { // use non global plugin for query + { + // use non global plugin for query actionKeyword = possibleActionKeyword; search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty; searchTerms = terms[1..]; } else - { // non action keyword + { + // non action keyword actionKeyword = string.Empty; search = rawQuery.TrimStart(); searchTerms = terms; diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index 7fb9b895a24..920abc28426 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -67,6 +67,7 @@ public void UpdatePluginSettings(List metadatas) metadata.Disabled = settings.Disabled; metadata.Priority = settings.Priority; metadata.SearchDelayTime = settings.SearchDelayTime; + metadata.HomeDisabled = settings.HomeDisabled; } else { @@ -79,6 +80,7 @@ public void UpdatePluginSettings(List metadatas) DefaultActionKeywords = metadata.ActionKeywords, // metadata provides default values ActionKeywords = metadata.ActionKeywords, // use default value Disabled = metadata.Disabled, + HomeDisabled = metadata.HomeDisabled, Priority = metadata.Priority, DefaultSearchDelayTime = metadata.SearchDelayTime, // metadata provides default values SearchDelayTime = metadata.SearchDelayTime, // use default value @@ -128,5 +130,6 @@ public class Plugin /// Used only to save the state of the plugin in settings /// public bool Disabled { get; set; } + public bool HomeDisabled { get; set; } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index b7a1d1f6367..34bf4f90e5c 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -158,6 +158,24 @@ public string PlaceholderText } } } + + private bool _showHomePage { get; set; } = true; + public bool ShowHomePage + { + get => _showHomePage; + set + { + if (_showHomePage != value) + { + _showHomePage = value; + OnPropertyChanged(); + } + } + } + + public bool ShowHistoryResultsForHomePage { get; set; } = false; + public int MaxHistoryResultsToShowForHomePage { get; set; } = 5; + public int CustomExplorerIndex { get; set; } = 0; [JsonIgnore] diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs new file mode 100644 index 00000000000..78d6454ae5f --- /dev/null +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin +{ + /// + /// Asynchronous Query Model for Flow Launcher When Query Text is Empty + /// + public interface IAsyncHomeQuery : IFeatures + { + /// + /// Asynchronous Querying When Query Text is Empty + /// + /// + /// If the Querying method requires high IO transmission + /// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncHomeQuery interface + /// + /// Cancel when querying job is obsolete + /// + Task> HomeQueryAsync(CancellationToken token); + } +} diff --git a/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs b/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs new file mode 100644 index 00000000000..81186fca2de --- /dev/null +++ b/Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin +{ + /// + /// Synchronous Query Model for Flow Launcher When Query Text is Empty + /// + /// If the Querying method requires high IO transmission + /// or performing CPU intense jobs (performing better with cancellation), please try the IAsyncHomeQuery interface + /// + /// + public interface IHomeQuery : IAsyncHomeQuery + { + /// + /// Querying When Query Text is Empty + /// + /// This method will be called within a Task.Run, + /// so please avoid synchronously wait for long. + /// + /// + /// + List HomeQuery(); + + Task> IAsyncHomeQuery.HomeQueryAsync(CancellationToken token) => Task.Run(HomeQuery); + } +} diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index da10bc6a504..09803cbd7cc 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -50,6 +50,11 @@ public class PluginMetadata : BaseModel /// public bool Disabled { get; set; } + /// + /// Whether plugin is disabled in home query. + /// + public bool HomeDisabled { get; set; } + /// /// Plugin execute file path. /// diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index ca0ed33b514..22ab2016cc0 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -126,6 +126,11 @@ Open Use Previous Korean IME You can change the Previous Korean IME settings directly from here + Home Page + Show home page results when query text is empty. + Show History 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. Search Plugin @@ -148,6 +153,7 @@ Enabled Priority Search Delay + Home Page Current Priority New Priority Priority @@ -401,6 +407,10 @@ Search Delay Time Setting Input the search delay time in ms you like to use for the plugin. Input empty if you don't want to specify any, and the plugin will use default search delay time. + + Home Page + Enable the plugin home page state if you like to show the plugin results when query is empty. + Custom Query Hotkey Press a custom hotkey to open Flow Launcher and input the specified query automatically. diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index ae7b098a206..e2948c54043 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -277,6 +277,12 @@ private async void OnLoaded(object sender, RoutedEventArgs _) case nameof(Settings.SettingWindowFont): InitializeContextMenu(); break; + case nameof(Settings.ShowHomePage): + if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText)) + { + _viewModel.QueryResults(); + } + break; } }; @@ -292,6 +298,12 @@ private async void OnLoaded(object sender, RoutedEventArgs _) DependencyPropertyDescriptor .FromProperty(VisibilityProperty, typeof(StackPanel)) .AddValueChanged(History, (s, e) => UpdateClockPanelVisibility()); + + // Initialize query state + if (_settings.ShowHomePage && string.IsNullOrEmpty(_viewModel.QueryText)) + { + _viewModel.QueryResults(); + } } private async void OnClosing(object sender, CancelEventArgs e) diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml index 619da22dc64..0842a64f345 100644 --- a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml @@ -100,10 +100,19 @@ ToolTipService.InitialShowDelay="0" ToolTipService.ShowOnDisabled="True" Value="{Binding PluginSearchDelayTime, Mode=TwoWay}" /> - + + $"{SearchDelayTimeValue}ms"; + + public int MaxHistoryResultsToShowValue + { + get => Settings.MaxHistoryResultsToShowForHomePage; + set + { + if (Settings.MaxHistoryResultsToShowForHomePage != value) + { + Settings.MaxHistoryResultsToShowForHomePage = value; + OnPropertyChanged(); + } + } + } private void UpdateEnumDropdownLocalizations() { diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs index abb314355b4..3e1294bc2d2 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -80,6 +80,20 @@ public bool IsSearchDelaySelected } } + private bool _isHomeOnOffSelected; + public bool IsHomeOnOffSelected + { + get => _isHomeOnOffSelected; + set + { + if (_isHomeOnOffSelected != value) + { + _isHomeOnOffSelected = value; + OnPropertyChanged(); + } + } + } + public SettingsPanePluginsViewModel(Settings settings) { _settings = settings; @@ -152,6 +166,18 @@ private async Task OpenHelperAsync(Button button) { Text = (string)Application.Current.Resources["searchDelayTimeTips"], TextWrapping = TextWrapping.Wrap + }, + new TextBlock + { + Text = (string)Application.Current.Resources["homeTitle"], + FontSize = 18, + Margin = new Thickness(0, 24, 0, 10), + TextWrapping = TextWrapping.Wrap + }, + new TextBlock + { + Text = (string)Application.Current.Resources["homeTips"], + TextWrapping = TextWrapping.Wrap } } }, @@ -176,16 +202,25 @@ private void UpdateDisplayModeFromSelection() IsOnOffSelected = false; IsPrioritySelected = true; IsSearchDelaySelected = false; + IsHomeOnOffSelected = false; break; case DisplayMode.SearchDelay: IsOnOffSelected = false; IsPrioritySelected = false; IsSearchDelaySelected = true; + IsHomeOnOffSelected = false; + break; + case DisplayMode.HomeOnOff: + IsOnOffSelected = false; + IsPrioritySelected = false; + IsSearchDelaySelected = false; + IsHomeOnOffSelected = true; break; default: IsOnOffSelected = true; IsPrioritySelected = false; IsSearchDelaySelected = false; + IsHomeOnOffSelected = false; break; } } @@ -195,5 +230,6 @@ public enum DisplayMode { OnOff, Priority, - SearchDelay + SearchDelay, + HomeOnOff } diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index d45d28d8bba..c0c5613de04 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -217,17 +217,46 @@ Title="{DynamicResource searchDelayTime}" Sub="{DynamicResource searchDelayTimeToolTip}" Type="InsideFit"> - - - + + + + + + + + + + + + + + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 228e66edb25..6c4236db9d1 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -33,6 +33,7 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private bool _isQueryRunning; private Query _lastQuery; + private bool _lastIsHomeQuery; private string _queryTextBeforeLeaveResults; private string _ignoredQueryText = null; @@ -51,6 +52,12 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private readonly IReadOnlyList _emptyResult = new List(); + private readonly PluginMetadata _historyMetadata = new() + { + ID = "298303A65D128A845D28A7B83B3968C2", // ID is for identifying the update plugin in UpdateActionAsync + Priority = 0 // Priority is for calculating scores in UpdateResultView + }; + #endregion #region Constructor @@ -783,8 +790,6 @@ private ResultsViewModel SelectedResults } } } - - _selectedResults.Visibility = Visibility.Visible; } } @@ -1078,6 +1083,11 @@ private bool QueryResultsPreviewed() #region Query + public void QueryResults() + { + _ = QueryResultsAsync(false); + } + public void Query(bool searchDelay, bool isReQuery = false) { if (_ignoredQueryText != null) @@ -1134,9 +1144,20 @@ private void QueryContextMenu() if (selected != null) // SelectedItem returns null if selection is empty. { - var results = PluginManager.GetContextMenusForPlugin(selected); - results.Add(ContextMenuTopMost(selected)); - results.Add(ContextMenuPluginInfo(selected.PluginID)); + List results; + if (selected.PluginID == null) // SelectedItem from history in home page. + { + results = new() + { + ContextMenuTopMost(selected) + }; + } + else + { + results = PluginManager.GetContextMenusForPlugin(selected); + results.Add(ContextMenuTopMost(selected)); + results.Add(ContextMenuPluginInfo(selected.PluginID)); + } if (!string.IsNullOrEmpty(query)) { @@ -1170,8 +1191,27 @@ private void QueryHistory() var query = QueryText.ToLower().Trim(); History.Clear(); + var results = GetHistoryItems(_history.Items); + + if (!string.IsNullOrEmpty(query)) + { + var filtered = results.Where + ( + r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || + App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() + ).ToList(); + History.AddResults(filtered, id); + } + else + { + History.AddResults(results, id); + } + } + + private static List GetHistoryItems(IEnumerable historyItems) + { var results = new List(); - foreach (var h in _history.Items) + foreach (var h in historyItems) { var title = App.API.GetTranslation("executeQuery"); var time = App.API.GetTranslation("lastExecuteTime"); @@ -1179,36 +1219,19 @@ private void QueryHistory() { Title = string.Format(title, h.Query), SubTitle = string.Format(time, h.ExecutedDateTime), - IcoPath = "Images\\history.png", - Preview = new Result.PreviewInfo - { - PreviewImagePath = Constant.HistoryIcon, - Description = string.Format(time, 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); } - - if (!string.IsNullOrEmpty(query)) - { - var filtered = results.Where - ( - r => App.API.FuzzySearch(query, r.Title).IsSearchPrecisionScoreMet() || - App.API.FuzzySearch(query, r.SubTitle).IsSearchPrecisionScoreMet() - ).ToList(); - History.AddResults(filtered, id); - } - else - { - History.AddResults(results, id); - } + return results; } private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) @@ -1239,6 +1262,8 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>"); + var isHomeQuery = query.RawQuery == string.Empty; + _updateSource = new CancellationTokenSource(); ProgressBarVisibility = Visibility.Hidden; @@ -1253,27 +1278,43 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b query.IsReQuery = isReQuery; // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); + RemoveOldQueryResults(query, isHomeQuery); _lastQuery = query; + _lastIsHomeQuery = isHomeQuery; - var plugins = PluginManager.ValidPluginsForQuery(query); - - var validPluginNames = plugins.Select(x => $"<{x.Metadata.Name}>"); - App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", validPluginNames)}"); - - if (plugins.Count == 1) - { - PluginIconPath = plugins.Single().Metadata.IcoPath; - PluginIconSource = await App.API.LoadImageAsync(PluginIconPath); - SearchIconVisibility = Visibility.Hidden; - } - else + ICollection plugins = Array.Empty(); + if (isHomeQuery) { + if (Settings.ShowHomePage) + { + plugins = PluginManager.ValidPluginsForHomeQuery(); + } + 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; + } + } + + var validPluginNames = plugins.Select(x => $"<{x.Metadata.Name}>"); + App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", validPluginNames)}"); // Do not wait for performance improvement /*if (string.IsNullOrEmpty(query.ActionKeyword)) @@ -1299,11 +1340,29 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // plugins are ICollection, meaning LINQ will get the Count and preallocate Array - var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch + Task[] tasks; + if (isHomeQuery) { - false => QueryTaskAsync(plugin, _updateSource.Token), - true => Task.CompletedTask - }).ToArray(); + tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch + { + false => QueryTaskAsync(plugin, _updateSource.Token), + true => Task.CompletedTask + }).ToArray(); + + // Query history results for home page firstly so it will be put on top of the results + if (Settings.ShowHistoryResultsForHomePage) + { + QueryHistoryTask(); + } + } + else + { + tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch + { + false => QueryTaskAsync(plugin, _updateSource.Token), + true => Task.CompletedTask + }).ToArray(); + } try { @@ -1332,7 +1391,7 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) { App.API.LogDebug(ClassName, $"Wait for querying plugin <{plugin.Metadata.Name}>"); - if (searchDelay) + if (searchDelay && !isHomeQuery) // Do not delay for home query { var searchDelayTime = plugin.Metadata.SearchDelayTime ?? Settings.SearchDelayTime; @@ -1345,7 +1404,9 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) // Task.Yield will force it to run in ThreadPool await Task.Yield(); - var results = await PluginManager.QueryForPluginAsync(plugin, query, token); + var results = isHomeQuery ? + await PluginManager.QueryHomeForPluginAsync(plugin, query, token) : + await PluginManager.QueryForPluginAsync(plugin, query, token); if (token.IsCancellationRequested) return; @@ -1378,6 +1439,24 @@ async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } } + + void QueryHistoryTask() + { + // 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 results = GetHistoryItems(historyItems); + + if (_updateSource.Token.IsCancellationRequested) return; + + App.API.LogDebug(ClassName, $"Update results for history"); + + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, + _updateSource.Token))) + { + App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); + } + } } private async Task ConstructQueryAsync(string queryText, IEnumerable customShortcuts, @@ -1385,7 +1464,7 @@ private async Task ConstructQueryAsync(string queryText, IEnumerable builtIn } } - private void RemoveOldQueryResults(Query query) + private void RemoveOldQueryResults(Query query, bool isHomeQuery) { - if (_lastQuery?.ActionKeyword != query?.ActionKeyword) + // If last and current query are home query, we don't need to clear the results + if (_lastIsHomeQuery && isHomeQuery) + { + return; + } + // If last or current query is home query, we need to clear the results + else if (_lastIsHomeQuery || isHomeQuery) + { + App.API.LogDebug(ClassName, $"Remove old results"); + Results.Clear(); + } + // If last and current query are not home query, we need to check action keyword + else if (_lastQuery?.ActionKeyword != query?.ActionKeyword) { App.API.LogDebug(ClassName, $"Remove old results"); - Results.Clear(); } } @@ -1483,7 +1573,8 @@ private Result ContextMenuTopMost(Result result) App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ReQuery(); return false; - } + }, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE74B") }; } else @@ -1492,7 +1583,6 @@ private Result ContextMenuTopMost(Result result) { Title = App.API.GetTranslation("setAsTopMostInThisQuery"), IcoPath = "Images\\up.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\xeac2"), PluginDirectory = Constant.ProgramDirectory, Action = _ => { @@ -1500,7 +1590,8 @@ private Result ContextMenuTopMost(Result result) App.API.ShowMsg(App.API.GetTranslation("success")); App.API.ReQuery(); return false; - } + }, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE74A") }; } @@ -1649,7 +1740,7 @@ public async void Hide() break; case LastQueryMode.ActionKeywordPreserved: case LastQueryMode.ActionKeywordSelected: - var newQuery = _lastQuery.ActionKeyword; + var newQuery = _lastQuery?.ActionKeyword; if (!string.IsNullOrEmpty(newQuery)) newQuery += " "; diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 64a7f3db84e..01fa3d20326 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -75,6 +75,16 @@ public bool PluginState } } + public bool PluginHomeState + { + get => !PluginPair.Metadata.HomeDisabled; + set + { + PluginPair.Metadata.HomeDisabled = !value; + PluginSettingsObject.HomeDisabled = !value; + } + } + public bool IsExpanded { get => _isExpanded; @@ -154,6 +164,7 @@ public Control SettingControl public Infrastructure.UserSettings.Plugin PluginSettingsObject{ get; init; } public bool SearchDelayEnabled => Settings.SearchQueryResultsWithDelay; public string DefaultSearchDelay => Settings.SearchDelayTime.ToString(); + public bool HomeEnabled => Settings.ShowHomePage && PluginManager.IsHomePlugin(PluginPair.Metadata.ID); public void OnActionKeywordsTextChanged() { diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs index 05e8d960fb0..48717816b0d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs @@ -3,21 +3,33 @@ namespace Flow.Launcher.Plugin.PluginIndicator { - public class Main : IPlugin, IPluginI18n + public class Main : IPlugin, IPluginI18n, IHomeQuery { internal PluginInitContext Context { get; private set; } public List Query(Query query) + { + return QueryResults(query); + } + + public List HomeQuery() + { + return QueryResults(); + } + + private List QueryResults(Query query = null) { var nonGlobalPlugins = GetNonGlobalPlugins(); + var querySearch = query?.Search ?? string.Empty; + var results = from keyword in nonGlobalPlugins.Keys let plugin = nonGlobalPlugins[keyword].Metadata - let keywordSearchResult = Context.API.FuzzySearch(query.Search, keyword) - let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : Context.API.FuzzySearch(query.Search, plugin.Name) + let keywordSearchResult = Context.API.FuzzySearch(querySearch, keyword) + let searchResult = keywordSearchResult.IsSearchPrecisionScoreMet() ? keywordSearchResult : Context.API.FuzzySearch(querySearch, plugin.Name) let score = searchResult.Score where (searchResult.IsSearchPrecisionScoreMet() - || string.IsNullOrEmpty(query.Search)) // To list all available action keywords + || string.IsNullOrEmpty(querySearch)) // To list all available action keywords && !plugin.Disabled select new Result {