Improve and fix query result update logic issue & provide access to exact query typed by user (#3502)

This commit is contained in:
Jack Ye 2025-11-26 18:15:12 +08:00 committed by GitHub
parent 38c0fae064
commit 48f67b1886
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 201 additions and 166 deletions

View file

@ -100,11 +100,11 @@ namespace Flow.Launcher.Core.Plugin
RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API));
RPC.AddLocalRpcMethod("UpdateResults", new Action<string, JsonRPCQueryResponseModel>((rawQuery, response) =>
RPC.AddLocalRpcMethod("UpdateResults", new Action<string, JsonRPCQueryResponseModel>((trimmedQuery, response) =>
{
var results = ParseResults(response);
ResultsUpdated?.Invoke(this,
new ResultUpdatedEventArgs { Query = new Query() { RawQuery = rawQuery }, Results = results });
new ResultUpdatedEventArgs { Query = new Query() { TrimmedQuery = trimmedQuery }, Results = results });
}));
RPC.SynchronizationContext = null;
RPC.StartListening();

View file

@ -401,7 +401,7 @@ namespace Flow.Launcher.Core.Plugin
{
Title = Localize.pluginStillInitializing(metadata.Name),
SubTitle = Localize.pluginStillInitializingSubtitle(),
AutoCompleteText = query.RawQuery,
AutoCompleteText = query.TrimmedQuery,
IcoPath = metadata.IcoPath,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,
@ -443,7 +443,7 @@ namespace Flow.Launcher.Core.Plugin
{
Title = Localize.pluginFailedToRespond(metadata.Name),
SubTitle = Localize.pluginFailedToRespondSubtitle(),
AutoCompleteText = query.RawQuery,
AutoCompleteText = query.TrimmedQuery,
IcoPath = Constant.ErrorIcon,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,
@ -467,7 +467,7 @@ namespace Flow.Launcher.Core.Plugin
{
Title = Localize.pluginStillInitializing(metadata.Name),
SubTitle = Localize.pluginStillInitializingSubtitle(),
AutoCompleteText = query.RawQuery,
AutoCompleteText = query.TrimmedQuery,
IcoPath = metadata.IcoPath,
PluginDirectory = metadata.PluginDirectory,
ActionKeywordAssigned = query.ActionKeyword,

View file

@ -6,15 +6,16 @@ namespace Flow.Launcher.Core.Plugin
{
public static class QueryBuilder
{
public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins)
public static Query Build(string originalQuery, string trimmedQuery, Dictionary<string, PluginPair> nonGlobalPlugins)
{
// home query
if (string.IsNullOrEmpty(text))
if (string.IsNullOrEmpty(trimmedQuery))
{
return new Query()
{
Search = string.Empty,
RawQuery = string.Empty,
OriginalQuery = string.Empty,
TrimmedQuery = string.Empty,
SearchTerms = Array.Empty<string>(),
ActionKeyword = string.Empty,
IsHomeQuery = true
@ -22,14 +23,13 @@ namespace Flow.Launcher.Core.Plugin
}
// replace multiple white spaces with one white space
var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
var terms = trimmedQuery.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
if (terms.Length == 0)
{
// nothing was typed
return null;
}
var rawQuery = text;
string actionKeyword, search;
string possibleActionKeyword = terms[0];
string[] searchTerms;
@ -38,21 +38,22 @@ namespace Flow.Launcher.Core.Plugin
{
// use non global plugin for query
actionKeyword = possibleActionKeyword;
search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
search = terms.Length > 1 ? trimmedQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
searchTerms = terms[1..];
}
else
{
// non action keyword
actionKeyword = string.Empty;
search = rawQuery.TrimStart();
search = trimmedQuery.TrimStart();
searchTerms = terms;
}
return new Query()
{
Search = search,
RawQuery = rawQuery,
OriginalQuery = originalQuery,
TrimmedQuery = trimmedQuery,
SearchTerms = searchTerms,
ActionKeyword = actionKeyword,
IsHomeQuery = false

View file

@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System;
using System.Text.Json.Serialization;
namespace Flow.Launcher.Plugin
{
@ -8,11 +9,29 @@ namespace Flow.Launcher.Plugin
public class Query
{
/// <summary>
/// Raw query, this includes action keyword if it has.
/// It has handled buildin custom query shortkeys and build-in shortcuts, and it trims the whitespace.
/// We didn't recommend use this property directly. You should always use Search property.
/// Original query, exactly how the user has typed into the search box.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
public string RawQuery { get; internal init; }
public string OriginalQuery { get; internal init; }
/// <summary>
/// Raw query, this includes action keyword if it has.
/// It has handled built-in custom query hotkeys and built-in shortcuts, and it trims the whitespace.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
[Obsolete("RawQuery is renamed to TrimmedQuery. This property will be removed. Update the code to use TrimmedQuery instead.")]
public string RawQuery {
get => TrimmedQuery;
internal init { TrimmedQuery = value; }
}
/// <summary>
/// Original query but with trimmed whitespace. Includes action keyword.
/// It has handled built-in custom query hotkeys and build-in shortcuts.
/// If you need the exact original query from the search box, use OriginalQuery property instead.
/// We don't recommend using this property directly. You should always use Search property.
/// </summary>
public string TrimmedQuery { get; internal init; }
/// <summary>
/// Determines whether the query was forced to execute again.
@ -28,7 +47,7 @@ namespace Flow.Launcher.Plugin
/// <summary>
/// Search part of a query.
/// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as RawQuery.
/// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as TrimmedQuery.
/// Since we allow user to switch a exclusive plugin to generic plugin,
/// so this property will always give you the "real" query part of the query
/// </summary>
@ -103,6 +122,6 @@ namespace Flow.Launcher.Plugin
}
/// <inheritdoc />
public override string ToString() => RawQuery;
public override string ToString() => TrimmedQuery;
}
}

View file

@ -16,9 +16,9 @@ namespace Flow.Launcher.Test
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.TrimmedQuery);
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
ClassicAssert.AreEqual(">", q.ActionKeyword);
@ -39,10 +39,10 @@ namespace Flow.Launcher.Test
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
};
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.Search);
ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
ClassicAssert.AreEqual(q.Search, q.TrimmedQuery, "TrimmedQuery should be equal to Search.");
ClassicAssert.AreEqual(6, q.SearchTerms.Length, "The length of SearchTerms should match.");
ClassicAssert.AreNotEqual(">", q.ActionKeyword, "ActionKeyword should not match that of a disabled plugin.");
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.SecondToEndSearch, "SecondToEndSearch should be trimmed of multiple whitespace characters");
@ -51,7 +51,7 @@ namespace Flow.Launcher.Test
[Test]
public void GenericPluginQueryTest()
{
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
ClassicAssert.AreEqual("", q.ActionKeyword);

View file

@ -16,11 +16,11 @@ public static class ResultHelper
return await PopulateResultsAsync(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey);
}
public static async Task<Result?> PopulateResultsAsync(string pluginId, string rawQuery, string title, string subTitle, string recordKey)
public static async Task<Result?> PopulateResultsAsync(string pluginId, string trimmedQuery, string title, string subTitle, string recordKey)
{
var plugin = PluginManager.GetPluginForId(pluginId);
if (plugin == null) return null;
var query = QueryBuilder.Build(rawQuery, PluginManager.GetNonGlobalPlugins());
var query = QueryBuilder.Build(trimmedQuery, trimmedQuery, PluginManager.GetNonGlobalPlugins());
if (query == null) return null;
try
{

View file

@ -477,7 +477,7 @@ namespace Flow.Launcher
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
{
var queryWithoutActionKeyword =
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.GetNonGlobalPlugins())?.Search;
QueryBuilder.Build(QueryTextBox.Text, QueryTextBox.Text.Trim(), PluginManager.GetNonGlobalPlugins())?.Search;
if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword))
{

View file

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@ -9,7 +10,7 @@ namespace Flow.Launcher
{
public partial class ResultListBox
{
protected object _lock = new object();
protected Lock _lock = new();
private Point _lastpos;
private ListBoxItem curItem = null;
public ResultListBox()
@ -88,12 +89,11 @@ namespace Flow.Launcher
}
}
private Point start;
private string path;
private string query;
private Point _start;
private string _path;
private string _trimmedQuery;
// this method is called by the UI thread, which is single threaded, so we can be sloppy with locking
private bool isDragging;
private bool _isDragging;
private void ResultList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
@ -104,53 +104,55 @@ namespace Flow.Launcher
Result:
{
CopyText: { } copyText,
OriginQuery.RawQuery: { } rawQuery
OriginQuery.TrimmedQuery: { } trimmedQuery
}
}
}) return;
path = copyText;
query = rawQuery;
start = e.GetPosition(null);
isDragging = true;
_path = copyText;
_trimmedQuery = trimmedQuery;
_start = e.GetPosition(null);
_isDragging = true;
}
private void ResultList_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton != MouseButtonState.Pressed || !isDragging)
if (e.LeftButton != MouseButtonState.Pressed || !_isDragging)
{
start = default;
path = string.Empty;
query = string.Empty;
isDragging = false;
_start = default;
_path = string.Empty;
_trimmedQuery = string.Empty;
_isDragging = false;
return;
}
if (!File.Exists(path) && !Directory.Exists(path))
if (!File.Exists(_path) && !Directory.Exists(_path))
return;
Point mousePosition = e.GetPosition(null);
Vector diff = this.start - mousePosition;
Vector diff = _start - mousePosition;
if (Math.Abs(diff.X) < SystemParameters.MinimumHorizontalDragDistance
|| Math.Abs(diff.Y) < SystemParameters.MinimumVerticalDragDistance)
return;
isDragging = false;
_isDragging = false;
App.API.HideMainWindow();
var data = new DataObject(DataFormats.FileDrop, new[]
{
path
_path
});
// Reassigning query to a new variable because for some reason
// after DragDrop.DoDragDrop call, 'query' loses its content, i.e. becomes empty string
var rawQuery = query;
var trimmedQuery = _trimmedQuery;
var effect = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move | DragDropEffects.Copy);
if (effect == DragDropEffects.Move)
App.API.ChangeQuery(rawQuery, true);
App.API.ChangeQuery(trimmedQuery, true);
}
private void ResultListBox_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })
@ -158,6 +160,7 @@ namespace Flow.Launcher
RightClickResultCommand?.Execute(result.Result);
}
private void ResultListBox_OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result })

View file

@ -19,13 +19,13 @@ public class LastOpenedHistoryItem
return Title == r.Title
&& SubTitle == r.SubTitle
&& PluginID == r.PluginID
&& Query == r.OriginQuery.RawQuery;
&& Query == r.OriginQuery.TrimmedQuery;
}
else
{
return RecordKey == r.RecordKey
&& PluginID == r.PluginID
&& Query == r.OriginQuery.RawQuery;
&& Query == r.OriginQuery.TrimmedQuery;
}
}
}

View file

@ -35,7 +35,7 @@ namespace Flow.Launcher.Storage
public void Add(Result result)
{
if (string.IsNullOrEmpty(result.OriginQuery.RawQuery)) return;
if (string.IsNullOrEmpty(result.OriginQuery.TrimmedQuery)) return;
if (string.IsNullOrEmpty(result.PluginID)) return;
// Maintain the max history limit
@ -57,7 +57,7 @@ namespace Flow.Launcher.Storage
Title = result.Title,
SubTitle = result.SubTitle,
PluginID = result.PluginID,
Query = result.OriginQuery.RawQuery,
Query = result.OriginQuery.TrimmedQuery,
RecordKey = result.RecordKey,
ExecutedDateTime = DateTime.Now
});

View file

@ -113,7 +113,7 @@ namespace Flow.Launcher.Storage
internal bool IsTopMost(Result result)
{
if (records.IsEmpty || !records.TryGetValue(result.OriginQuery.RawQuery, out var value))
if (records.IsEmpty || !records.TryGetValue(result.OriginQuery.TrimmedQuery, out var value))
{
return false;
}
@ -124,7 +124,7 @@ namespace Flow.Launcher.Storage
internal void Remove(Result result)
{
records.Remove(result.OriginQuery.RawQuery, out _);
records.Remove(result.OriginQuery.TrimmedQuery, out _);
}
internal void AddOrUpdate(Result result)
@ -136,7 +136,7 @@ namespace Flow.Launcher.Storage
SubTitle = result.SubTitle,
RecordKey = result.RecordKey
};
records.AddOrUpdate(result.OriginQuery.RawQuery, record, (key, oldValue) => record);
records.AddOrUpdate(result.OriginQuery.TrimmedQuery, record, (key, oldValue) => record);
}
}
@ -154,7 +154,7 @@ namespace Flow.Launcher.Storage
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to check if the result is top most
if (records.IsEmpty || result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
!records.TryGetValue(result.OriginQuery.TrimmedQuery, out var value))
{
return false;
}
@ -168,7 +168,7 @@ namespace Flow.Launcher.Storage
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to check if the result is top most
if (records.IsEmpty || result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
!records.TryGetValue(result.OriginQuery.TrimmedQuery, out var value))
{
return -1;
}
@ -194,7 +194,7 @@ namespace Flow.Launcher.Storage
// origin query is null when user select the context menu item directly of one item from query list
// in this case, we do not need to remove the record
if (result.OriginQuery == null ||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
!records.TryGetValue(result.OriginQuery.TrimmedQuery, out var value))
{
return;
}
@ -204,12 +204,12 @@ namespace Flow.Launcher.Storage
if (queue.IsEmpty)
{
// if the queue is empty, remove the queue from the dictionary
records.TryRemove(result.OriginQuery.RawQuery, out _);
records.TryRemove(result.OriginQuery.TrimmedQuery, out _);
}
else
{
// change the queue in the dictionary
records[result.OriginQuery.RawQuery] = queue;
records[result.OriginQuery.TrimmedQuery] = queue;
}
}
@ -229,19 +229,19 @@ namespace Flow.Launcher.Storage
SubTitle = result.SubTitle,
RecordKey = result.RecordKey
};
if (!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
if (!records.TryGetValue(result.OriginQuery.TrimmedQuery, out var value))
{
// create a new queue if it does not exist
value = new ConcurrentQueue<Record>();
value.Enqueue(record);
records.TryAdd(result.OriginQuery.RawQuery, value);
records.TryAdd(result.OriginQuery.TrimmedQuery, value);
}
else
{
// add or update the record in the queue
var queue = new ConcurrentQueue<Record>(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates
queue.Enqueue(record);
records[result.OriginQuery.RawQuery] = queue;
records[result.OriginQuery.TrimmedQuery] = queue;
}
}
}

View file

@ -35,9 +35,10 @@ namespace Flow.Launcher.ViewModel
private static readonly string ClassName = nameof(MainViewModel);
private bool _isQueryRunning;
private Query _lastQuery;
private bool _previousIsHomeQuery;
private Query _progressQuery; // Used for QueryResultAsync
private Query _updateQuery; // Used for ResultsUpdated
private string _queryTextBeforeLeaveResults;
private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results
@ -283,7 +284,7 @@ namespace Flow.Launcher.ViewModel
plugin.ResultsUpdated += (s, e) =>
{
if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested)
if (_updateQuery == null || e.Query.OriginalQuery != _updateQuery.OriginalQuery || e.Token.IsCancellationRequested)
{
return;
}
@ -442,7 +443,7 @@ namespace Flow.Launcher.ViewModel
[RelayCommand]
private void Backspace(object index)
{
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.GetNonGlobalPlugins());
var query = QueryBuilder.Build(QueryText, QueryText.Trim(), PluginManager.GetNonGlobalPlugins());
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
@ -1327,7 +1328,7 @@ namespace Flow.Launcher.ViewModel
Title = Localize.executeQuery(h.Query),
SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { RawQuery = h.Query },
OriginQuery = new Query { TrimmedQuery = h.Query },
Action = _ =>
{
App.API.BackToQueryResults();
@ -1350,7 +1351,7 @@ namespace Flow.Launcher.ViewModel
h.Title,
SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
IcoPath = Constant.HistoryIcon,
OriginQuery = new Query { RawQuery = h.Query },
OriginQuery = new Query { TrimmedQuery = h.Query },
AsyncAction = async c =>
{
var reflectResult = await ResultHelper.PopulateResultsAsync(h);
@ -1391,7 +1392,7 @@ namespace Flow.Launcher.ViewModel
return;
}
App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>");
App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and TrimmedQuery <{query.TrimmedQuery}>");
var currentIsHomeQuery = query.IsHomeQuery;
var currentIsDialogJump = _isDialogJump;
@ -1403,69 +1404,73 @@ namespace Flow.Launcher.ViewModel
return;
}
_updateSource?.Dispose();
var currentUpdateSource = new CancellationTokenSource();
_updateSource = currentUpdateSource;
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;
ProgressBarVisibility = Visibility.Hidden;
_isQueryRunning = true;
// Switch to ThreadPool thread
await TaskScheduler.Default;
if (currentCancellationToken.IsCancellationRequested) return;
// Update the query's IsReQuery property to true if this is a re-query
query.IsReQuery = isReQuery;
ICollection<PluginPair> plugins = Array.Empty<PluginPair>();
if (currentIsHomeQuery)
try
{
if (Settings.ShowHomePage)
{
plugins = PluginManager.ValidPluginsForHomeQuery();
}
_updateSource?.Dispose();
PluginIconPath = null;
PluginIconSource = null;
SearchIconVisibility = Visibility.Visible;
}
else
{
plugins = PluginManager.ValidPluginsForQuery(query, currentIsDialogJump);
var currentUpdateSource = new CancellationTokenSource();
_updateSource = currentUpdateSource;
var currentCancellationToken = _updateSource.Token;
_updateToken = currentCancellationToken;
if (plugins.Count == 1)
{
PluginIconPath = plugins.Single().Metadata.IcoPath;
PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
SearchIconVisibility = Visibility.Hidden;
}
else
ProgressBarVisibility = Visibility.Hidden;
_progressQuery = query;
_updateQuery = query;
// Switch to ThreadPool thread
await TaskScheduler.Default;
if (currentCancellationToken.IsCancellationRequested) return;
// Update the query's IsReQuery property to true if this is a re-query
query.IsReQuery = isReQuery;
ICollection<PluginPair> plugins = Array.Empty<PluginPair>();
if (currentIsHomeQuery)
{
if (Settings.ShowHomePage)
{
plugins = PluginManager.ValidPluginsForHomeQuery();
}
PluginIconPath = null;
PluginIconSource = null;
SearchIconVisibility = Visibility.Visible;
}
}
else
{
plugins = PluginManager.ValidPluginsForQuery(query, currentIsDialogJump);
App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", plugins.Select(x => $"<{x.Metadata.Name}>"))}");
if (plugins.Count == 1)
{
PluginIconPath = plugins.Single().Metadata.IcoPath;
PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
SearchIconVisibility = Visibility.Hidden;
}
else
{
PluginIconPath = null;
PluginIconSource = null;
SearchIconVisibility = Visibility.Visible;
}
}
// Do not wait for performance improvement
/*if (string.IsNullOrEmpty(query.ActionKeyword))
{
// Wait 15 millisecond for query change in global query
// if query changes, return so that it won't be calculated
await Task.Delay(15, currentCancellationToken);
if (currentCancellationToken.IsCancellationRequested) return;
}*/
App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", plugins.Select(x => $"<{x.Metadata.Name}>"))}");
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
// Do not wait for performance improvement
/*if (string.IsNullOrEmpty(query.ActionKeyword))
{
// Wait 15 millisecond for query change in global query
// if query changes, return so that it won't be calculated
await Task.Delay(15, currentCancellationToken);
if (currentCancellationToken.IsCancellationRequested) return;
}*/
_ = Task.Delay(200, currentCancellationToken).ContinueWith(_ =>
{
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
if (_isQueryRunning)
if (_progressQuery != null && _progressQuery.OriginalQuery == query.OriginalQuery)
{
ProgressBarVisibility = Visibility.Visible;
}
@ -1474,58 +1479,65 @@ namespace Flow.Launcher.ViewModel
TaskContinuationOptions.NotOnCanceled,
TaskScheduler.Default);
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
Task[] tasks;
if (currentIsHomeQuery)
{
if (ShouldClearExistingResultsForNonQuery(plugins))
Task[] tasks;
if (currentIsHomeQuery)
{
Results.Clear();
App.API.LogDebug(ClassName, $"Existing results are cleared for non-query");
if (ShouldClearExistingResultsForNonQuery(plugins))
{
// there are no update tasks and so we can directly return
ClearResults();
return;
}
tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch
{
false => QueryTaskAsync(plugin, currentCancellationToken),
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(currentCancellationToken);
}
}
else
{
tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
{
false => QueryTaskAsync(plugin, currentCancellationToken),
true => Task.CompletedTask
}).ToArray();
}
tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch
try
{
false => QueryTaskAsync(plugin, currentCancellationToken),
true => Task.CompletedTask
}).ToArray();
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
// nothing to do here
}
// Query history results for home page firstly so it will be put on top of the results
if (Settings.ShowHistoryResultsForHomePage)
if (currentCancellationToken.IsCancellationRequested) return;
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
_progressQuery = null;
if (!currentCancellationToken.IsCancellationRequested)
{
QueryHistoryTask(currentCancellationToken);
// update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
}
}
else
finally
{
tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
{
false => QueryTaskAsync(plugin, currentCancellationToken),
true => Task.CompletedTask
}).ToArray();
}
try
{
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
// nothing to do here
}
if (currentCancellationToken.IsCancellationRequested) return;
// this should happen once after all queries are done so progress bar should continue
// until the end of all querying
_isQueryRunning = false;
if (!currentCancellationToken.IsCancellationRequested)
{
// update to hidden if this is still the current query
ProgressBarVisibility = Visibility.Hidden;
// this make sures progress query is null when this query is canceled
_progressQuery = null;
}
// Local function
@ -1625,7 +1637,7 @@ namespace Flow.Launcher.ViewModel
{
if (string.IsNullOrWhiteSpace(queryText))
{
return QueryBuilder.Build(string.Empty, PluginManager.GetNonGlobalPlugins());
return QueryBuilder.Build(string.Empty, string.Empty, PluginManager.GetNonGlobalPlugins());
}
var queryBuilder = new StringBuilder(queryText);
@ -1645,7 +1657,7 @@ namespace Flow.Launcher.ViewModel
// Applying builtin shortcuts
await BuildQueryAsync(builtInShortcuts, queryBuilder, queryBuilderTmp);
return QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.GetNonGlobalPlugins());
return QueryBuilder.Build(queryText, queryBuilder.ToString().Trim(), PluginManager.GetNonGlobalPlugins());
}
private async Task BuildQueryAsync(IEnumerable<BaseBuiltinShortcutModel> builtInShortcuts,

View file

@ -172,7 +172,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
Action = c =>
{
Settings.EnableEverythingContentSearch = true;
Context.API.ChangeQuery(query.RawQuery, true);
Context.API.ChangeQuery(query.TrimmedQuery, true);
return false;
}
}