mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3399 from Jack251970/empty_query
Support Querying Results When Query Text is Empty
This commit is contained in:
commit
6799b87f96
16 changed files with 436 additions and 79 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
23
Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs
Normal file
23
Flow.Launcher.Plugin/Interfaces/IAsyncHomeQuery.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
28
Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs
Normal file
28
Flow.Launcher.Plugin/Interfaces/IHomeQuery.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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=""
|
||||
Sub="{DynamicResource homePageToolTip}">
|
||||
<ui:ToggleSwitch
|
||||
IsOn="{Binding Settings.ShowHomePage}"
|
||||
OffContent="{DynamicResource disable}"
|
||||
OnContent="{DynamicResource enable}" />
|
||||
</cc:Card>
|
||||
|
||||
<cc:ExCard Title="{DynamicResource historyResultsForHomePage}" Icon="">
|
||||
<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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 += " ";
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue