Merge pull request #3399 from Jack251970/empty_query

Support Querying Results When Query Text is Empty
This commit is contained in:
Jeremy Wu 2025-05-05 23:16:59 +10:00 committed by GitHub
commit 6799b87f96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 436 additions and 79 deletions

View file

@ -25,6 +25,7 @@ namespace Flow.Launcher.Core.Plugin
private static readonly string ClassName = nameof(PluginManager);
private static IEnumerable<PluginPair> _contextMenuPlugins;
private static IEnumerable<PluginPair> _homePlugins;
public static List<PluginPair> AllPlugins { get; private set; }
public static readonly HashSet<PluginPair> 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<IContextMenu>();
_homePlugins = GetPluginsForInterface<IAsyncHomeQuery>();
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<PluginPair> ValidPluginsForHomeQuery()
{
return _homePlugins.ToList();
}
public static async Task<List<Result>> QueryForPluginAsync(PluginPair pair, Query query, CancellationToken token)
{
var results = new List<Result>();
@ -318,6 +327,36 @@ namespace Flow.Launcher.Core.Plugin
return results;
}
public static async Task<List<Result>> QueryHomeForPluginAsync(PluginPair pair, Query query, CancellationToken token)
{
var results = new List<Result>();
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<Result> 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

View file

@ -8,10 +8,23 @@ namespace Flow.Launcher.Core.Plugin
{
public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins)
{
// home query
if (string.IsNullOrEmpty(text))
{
return new Query()
{
Search = string.Empty,
RawQuery = string.Empty,
SearchTerms = Array.Empty<string>(),
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;

View file

@ -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
/// </summary>
public bool Disabled { get; set; }
public bool HomeDisabled { get; set; }
}
}

View file

@ -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]

View file

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Asynchronous Query Model for Flow Launcher When Query Text is Empty
/// </summary>
public interface IAsyncHomeQuery : IFeatures
{
/// <summary>
/// Asynchronous Querying When Query Text is Empty
/// </summary>
/// <para>
/// If the Querying method requires high IO transmission
/// or performing CPU intense jobs (performing better with cancellation), please use this IAsyncHomeQuery interface
/// </para>
/// <param name="token">Cancel when querying job is obsolete</param>
/// <returns></returns>
Task<List<Result>> HomeQueryAsync(CancellationToken token);
}
}

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Flow.Launcher.Plugin
{
/// <summary>
/// Synchronous Query Model for Flow Launcher When Query Text is Empty
/// <para>
/// If the Querying method requires high IO transmission
/// or performing CPU intense jobs (performing better with cancellation), please try the IAsyncHomeQuery interface
/// </para>
/// </summary>
public interface IHomeQuery : IAsyncHomeQuery
{
/// <summary>
/// Querying When Query Text is Empty
/// <para>
/// This method will be called within a Task.Run,
/// so please avoid synchronously wait for long.
/// </para>
/// </summary>
/// <returns></returns>
List<Result> HomeQuery();
Task<List<Result>> IAsyncHomeQuery.HomeQueryAsync(CancellationToken token) => Task.Run(HomeQuery);
}
}

View file

@ -50,6 +50,11 @@ namespace Flow.Launcher.Plugin
/// </summary>
public bool Disabled { get; set; }
/// <summary>
/// Whether plugin is disabled in home query.
/// </summary>
public bool HomeDisabled { get; set; }
/// <summary>
/// Plugin execute file path.
/// </summary>

View file

@ -126,6 +126,11 @@
<system:String x:Key="KoreanImeOpenLinkButton">Open</system:String>
<system:String x:Key="KoreanImeRegistry">Use Previous Korean IME</system:String>
<system:String x:Key="KoreanImeRegistryTooltip">You can change the Previous Korean IME settings directly from here</system:String>
<system:String x:Key="homePage">Home Page</system:String>
<system:String x:Key="homePageToolTip">Show home page results when query text is empty.</system:String>
<system:String x:Key="historyResultsForHomePage">Show History Results in Home Page</system:String>
<system:String x:Key="historyResultsCountForHomePage">Maximum History Results Shown in Home Page</system:String>
<system:String x:Key="homeToggleBoxToolTip">This can only be edited if plugin supports Home feature and Home Page is enabled.</system:String>
<!-- Setting Plugin -->
<system:String x:Key="searchplugin">Search Plugin</system:String>
@ -148,6 +153,7 @@
<system:String x:Key="DisplayModeOnOff">Enabled</system:String>
<system:String x:Key="DisplayModePriority">Priority</system:String>
<system:String x:Key="DisplayModeSearchDelay">Search Delay</system:String>
<system:String x:Key="DisplayModeHomeOnOff">Home Page</system:String>
<system:String x:Key="currentPriority">Current Priority</system:String>
<system:String x:Key="newPriority">New Priority</system:String>
<system:String x:Key="priority">Priority</system:String>
@ -401,6 +407,10 @@
<system:String x:Key="searchDelayTimeTitle">Search Delay Time Setting</system:String>
<system:String x:Key="searchDelayTimeTips">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.</system:String>
<!-- Search Delay Settings Dialog -->
<system:String x:Key="homeTitle">Home Page</system:String>
<system:String x:Key="homeTips">Enable the plugin home page state if you like to show the plugin results when query is empty.</system:String>
<!-- Custom Query Hotkey Dialog -->
<system:String x:Key="customeQueryHotkeyTitle">Custom Query Hotkey</system:String>
<system:String x:Key="customeQueryHotkeyTips">Press a custom hotkey to open Flow Launcher and input the specified query automatically.</system:String>

View file

@ -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)

View file

@ -100,10 +100,19 @@
ToolTipService.InitialShowDelay="0"
ToolTipService.ShowOnDisabled="True"
Value="{Binding PluginSearchDelayTime, Mode=TwoWay}" />
</StackPanel>
<!-- Put OnOffControl after PriorityControl & SearchDelayControl so that it can display correctly -->
<ui:ToggleSwitch
x:Name="HomeOnOffControl"
Margin="0 0 8 0"
IsEnabled="{Binding HomeEnabled}"
IsOn="{Binding PluginHomeState}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}"
ToolTip="{DynamicResource homeToggleBoxToolTip}"
Visibility="{Binding DataContext.IsHomeOnOffSelected, RelativeSource={RelativeSource AncestorType=ListBox}, Converter={StaticResource BooleanToVisibilityConverter}}" />
<ui:ToggleSwitch
x:Name="OnOffControl"
Margin="0 0 8 0"

View file

@ -154,11 +154,22 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
{
Settings.SearchDelayTime = value;
OnPropertyChanged();
OnPropertyChanged(nameof(SearchDelayTimeDisplay));
}
}
}
public string SearchDelayTimeDisplay => $"{SearchDelayTimeValue}ms";
public int MaxHistoryResultsToShowValue
{
get => Settings.MaxHistoryResultsToShowForHomePage;
set
{
if (Settings.MaxHistoryResultsToShowForHomePage != value)
{
Settings.MaxHistoryResultsToShowForHomePage = value;
OnPropertyChanged();
}
}
}
private void UpdateEnumDropdownLocalizations()
{

View file

@ -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
}

View file

@ -217,17 +217,46 @@
Title="{DynamicResource searchDelayTime}"
Sub="{DynamicResource searchDelayTimeToolTip}"
Type="InsideFit">
<StackPanel Orientation="Horizontal">
<ui:NumberBox
Width="120"
Margin="0 0 0 0"
Maximum="1000"
Minimum="0"
SmallChange="10"
SpinButtonPlacementMode="Compact"
ValidationMode="InvalidInputOverwritten"
Value="{Binding SearchDelayTimeValue}" />
</StackPanel>
<ui:NumberBox
Width="120"
Margin="0 0 0 0"
Maximum="1000"
Minimum="0"
SmallChange="10"
SpinButtonPlacementMode="Compact"
ValidationMode="InvalidInputOverwritten"
Value="{Binding SearchDelayTimeValue}" />
</cc:Card>
</cc:ExCard>
<cc:Card
Title="{DynamicResource homePage}"
Margin="0 14 0 0"
Icon="&#xE80F;"
Sub="{DynamicResource homePageToolTip}">
<ui:ToggleSwitch
IsOn="{Binding Settings.ShowHomePage}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:ExCard Title="{DynamicResource historyResultsForHomePage}" Icon="&#xE81C;">
<cc:ExCard.SideContent>
<ui:ToggleSwitch
IsOn="{Binding Settings.ShowHistoryResultsForHomePage}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:ExCard.SideContent>
<cc:Card Title="{DynamicResource historyResultsCountForHomePage}" Type="InsideFit">
<ui:NumberBox
Width="120"
Margin="0 0 0 0"
Maximum="100"
Minimum="0"
SmallChange="5"
SpinButtonPlacementMode="Compact"
ValidationMode="InvalidInputOverwritten"
Value="{Binding MaxHistoryResultsToShowValue}" />
</cc:Card>
</cc:ExCard>

View file

@ -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<Result> _emptyResult = new List<Result>();
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<Result> 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<Result>();
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<Result> GetHistoryItems(IEnumerable<HistoryItem> historyItems)
{
var results = new List<Result>();
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<PluginPair> plugins = Array.Empty<PluginPair>();
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<Query> ConstructQueryAsync(string queryText, IEnumerable<CustomShortcutModel> 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 += " ";

View file

@ -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()
{

View file

@ -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<Result> Query(Query query)
{
return QueryResults(query);
}
public List<Result> HomeQuery()
{
return QueryResults();
}
private List<Result> 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
{