diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 72303c8b7..a3e80a73f 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(); @@ -220,6 +221,7 @@ namespace Flow.Launcher.Core.Plugin { 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 @@ 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 +278,11 @@ namespace Flow.Launcher.Core.Plugin }; } + 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 @@ namespace Flow.Launcher.Core.Plugin 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 @@ namespace Flow.Launcher.Core.Plugin 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 0ef3f30f5..fae821736 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -8,10 +8,23 @@ namespace Flow.Launcher.Core.Plugin { 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 @@ namespace Flow.Launcher.Core.Plugin 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 7fb9b895a..920abc284 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -67,6 +67,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings metadata.Disabled = settings.Disabled; metadata.Priority = settings.Priority; metadata.SearchDelayTime = settings.SearchDelayTime; + metadata.HomeDisabled = settings.HomeDisabled; } else { @@ -79,6 +80,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 @@ namespace Flow.Launcher.Infrastructure.UserSettings /// 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 b7a1d1f63..34bf4f90e 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -158,6 +158,24 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } } + + 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 000000000..78d6454ae --- /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 000000000..81186fca2 --- /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 da10bc6a5..09803cbd7 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -50,6 +50,11 @@ namespace Flow.Launcher.Plugin /// 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 ca0ed33b5..22ab2016c 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 ae7b098a2..e2948c540 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -277,6 +277,12 @@ namespace Flow.Launcher case nameof(Settings.SettingWindowFont): InitializeContextMenu(); break; + case nameof(Settings.ShowHomePage): + if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText)) + { + _viewModel.QueryResults(); + } + break; } }; @@ -292,6 +298,12 @@ namespace Flow.Launcher 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 619da22dc..0842a64f3 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 abb314355..3e1294bc2 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginsViewModel.cs @@ -80,6 +80,20 @@ public partial class SettingsPanePluginsViewModel : BaseModel } } + 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 @@ public partial class SettingsPanePluginsViewModel : BaseModel { 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 @@ public partial class SettingsPanePluginsViewModel : BaseModel 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 d45d28d8b..c0c5613de 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 228e66edb..6c4236db9 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -33,6 +33,7 @@ namespace Flow.Launcher.ViewModel private bool _isQueryRunning; private Query _lastQuery; + private bool _lastIsHomeQuery; private string _queryTextBeforeLeaveResults; private string _ignoredQueryText = null; @@ -51,6 +52,12 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel } } } - - _selectedResults.Visibility = Visibility.Visible; } } @@ -1078,6 +1083,11 @@ namespace Flow.Launcher.ViewModel #region Query + public void QueryResults() + { + _ = QueryResultsAsync(false); + } + public void Query(bool searchDelay, bool isReQuery = false) { if (_ignoredQueryText != null) @@ -1134,9 +1144,20 @@ namespace Flow.Launcher.ViewModel 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,31 +1191,7 @@ namespace Flow.Launcher.ViewModel var query = QueryText.ToLower().Trim(); History.Clear(); - var results = new List(); - foreach (var h in _history.Items) - { - var title = App.API.GetTranslation("executeQuery"); - var time = App.API.GetTranslation("lastExecuteTime"); - var result = new Result - { - 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) - }, - OriginQuery = new Query { RawQuery = h.Query }, - Action = _ => - { - App.API.BackToQueryResults(); - App.API.ChangeQuery(h.Query); - return false; - } - }; - results.Add(result); - } + var results = GetHistoryItems(_history.Items); if (!string.IsNullOrEmpty(query)) { @@ -1211,6 +1208,32 @@ namespace Flow.Launcher.ViewModel } } + private static List GetHistoryItems(IEnumerable historyItems) + { + var results = new List(); + foreach (var h in historyItems) + { + var title = App.API.GetTranslation("executeQuery"); + var time = App.API.GetTranslation("lastExecuteTime"); + var result = new Result + { + Title = string.Format(title, h.Query), + SubTitle = 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); + } + return results; + } + private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); @@ -1239,6 +1262,8 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel // 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 @@ namespace Flow.Launcher.ViewModel { 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 @@ 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 = isHomeQuery ? + await PluginManager.QueryHomeForPluginAsync(plugin, query, token) : + await PluginManager.QueryForPluginAsync(plugin, query, token); if (token.IsCancellationRequested) return; @@ -1378,6 +1439,24 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel { if (string.IsNullOrWhiteSpace(queryText)) { - return null; + return QueryBuilder.Build(string.Empty, PluginManager.NonGlobalPlugins); } var queryBuilder = new StringBuilder(queryText); @@ -1457,12 +1536,23 @@ namespace Flow.Launcher.ViewModel } } - 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel { 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 @@ namespace Flow.Launcher.ViewModel 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 @@ namespace Flow.Launcher.ViewModel 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 64a7f3db8..01fa3d203 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -75,6 +75,16 @@ namespace Flow.Launcher.ViewModel } } + public bool PluginHomeState + { + get => !PluginPair.Metadata.HomeDisabled; + set + { + PluginPair.Metadata.HomeDisabled = !value; + PluginSettingsObject.HomeDisabled = !value; + } + } + public bool IsExpanded { get => _isExpanded; @@ -154,6 +164,7 @@ namespace Flow.Launcher.ViewModel 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 05e8d960f..48717816b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Main.cs @@ -3,21 +3,33 @@ using System.Linq; 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 {