Skip to content

Commit 34c3cda

Browse files
committed
Improve update logic
1 parent 8bb96d7 commit 34c3cda

File tree

5 files changed

+105
-79
lines changed

5 files changed

+105
-79
lines changed

Flow.Launcher.Core/Plugin/QueryBuilder.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ namespace Flow.Launcher.Core.Plugin
66
{
77
public static class QueryBuilder
88
{
9-
public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalPlugins)
9+
public static Query Build(string input, string text, Dictionary<string, PluginPair> nonGlobalPlugins)
1010
{
1111
// replace multiple white spaces with one white space
1212
var terms = text.Split(Query.TermSeparator, StringSplitOptions.RemoveEmptyEntries);
1313
if (terms.Length == 0)
14-
{ // nothing was typed
14+
{
15+
// nothing was typed
1516
return null;
1617
}
1718

@@ -21,13 +22,15 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
2122
string[] searchTerms;
2223

2324
if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled)
24-
{ // use non global plugin for query
25+
{
26+
// use non global plugin for query
2527
actionKeyword = possibleActionKeyword;
2628
search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty;
2729
searchTerms = terms[1..];
2830
}
2931
else
30-
{ // non action keyword
32+
{
33+
// non action keyword
3134
actionKeyword = string.Empty;
3235
search = rawQuery.TrimStart();
3336
searchTerms = terms;
@@ -36,6 +39,7 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
3639
return new Query()
3740
{
3841
Search = search,
42+
Input = input,
3943
RawQuery = rawQuery,
4044
SearchTerms = searchTerms,
4145
ActionKeyword = actionKeyword

Flow.Launcher.Plugin/Query.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ namespace Flow.Launcher.Plugin
77
/// </summary>
88
public class Query
99
{
10+
/// <summary>
11+
/// Input text in query box.
12+
/// We didn't recommend use this property directly. You should always use Search property.
13+
/// </summary>
14+
public string Input { get; internal init; }
15+
1016
/// <summary>
1117
/// Raw query, this includes action keyword if it has.
1218
/// It has handled buildin custom query shortkeys and build-in shortcuts, and it trims the whitespace.

Flow.Launcher.Test/QueryBuilderTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public void ExclusivePluginQueryTest()
1616
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}}}}
1717
};
1818

19-
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
19+
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
2020

2121
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.RawQuery);
2222
ClassicAssert.AreEqual("ping google.com -n 20 -6", q.Search, "Search should not start with the ActionKeyword.");
@@ -39,7 +39,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest()
3939
{">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List<string> {">"}, Disabled = true}}}
4040
};
4141

42-
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", nonGlobalPlugins);
42+
Query q = QueryBuilder.Build("> ping google.com -n 20 -6", "> ping google.com -n 20 -6", nonGlobalPlugins);
4343

4444
ClassicAssert.AreEqual("> ping google.com -n 20 -6", q.Search);
4545
ClassicAssert.AreEqual(q.Search, q.RawQuery, "RawQuery should be equal to Search.");
@@ -51,7 +51,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest()
5151
[Test]
5252
public void GenericPluginQueryTest()
5353
{
54-
Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary<string, PluginPair>());
54+
Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary<string, PluginPair>());
5555

5656
ClassicAssert.AreEqual("file.txt file2 file3", q.Search);
5757
ClassicAssert.AreEqual("", q.ActionKeyword);

Flow.Launcher/MainWindow.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ private void OnKeyDown(object sender, KeyEventArgs e)
408408
&& QueryTextBox.CaretIndex == QueryTextBox.Text.Length)
409409
{
410410
var queryWithoutActionKeyword =
411-
QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
411+
QueryBuilder.Build(QueryTextBox.Text, QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search;
412412

413413
if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword))
414414
{

Flow.Launcher/ViewModel/MainViewModel.cs

Lines changed: 87 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable
3131

3232
private static readonly string ClassName = nameof(MainViewModel);
3333

34-
private bool _isQueryRunning;
3534
private Query _lastQuery;
35+
private Query _runningQuery; // Used for QueryResultAsync
36+
private Query _currentQuery; // Used for ResultsUpdated
3637
private string _queryTextBeforeLeaveResults;
3738

3839
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
@@ -235,7 +236,7 @@ public void RegisterResultsUpdatedEvent()
235236
var plugin = (IResultUpdated)pair.Plugin;
236237
plugin.ResultsUpdated += (s, e) =>
237238
{
238-
if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested)
239+
if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || e.Token.IsCancellationRequested)
239240
{
240241
return;
241242
}
@@ -255,9 +256,12 @@ public void RegisterResultsUpdatedEvent()
255256

256257
PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query);
257258

258-
if (token.IsCancellationRequested) return;
259+
if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || token.IsCancellationRequested)
260+
{
261+
return;
262+
}
259263

260-
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
264+
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
261265
token)))
262266
{
263267
App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
@@ -365,7 +369,7 @@ private void LoadContextMenu()
365369
[RelayCommand]
366370
private void Backspace(object index)
367371
{
368-
var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins);
372+
var query = QueryBuilder.Build(QueryText, QueryText.Trim(), PluginManager.NonGlobalPlugins);
369373

370374
// GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string
371375
var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\'));
@@ -786,7 +790,7 @@ private ResultsViewModel SelectedResults
786790

787791
public Visibility ProgressBarVisibility { get; set; }
788792
public Visibility MainWindowVisibility { get; set; }
789-
793+
790794
// This is to be used for determining the visibility status of the main window instead of MainWindowVisibility
791795
// because it is more accurate and reliable representation than using Visibility as a condition check
792796
public bool MainWindowVisibilityStatus { get; set; } = true;
@@ -1063,7 +1067,7 @@ private bool CanExternalPreviewSelectedResult(out string path)
10631067
path = QueryResultsPreviewed() ? Results.SelectedItem?.Result?.Preview.FilePath : string.Empty;
10641068
return !string.IsNullOrEmpty(path);
10651069
}
1066-
1070+
10671071
private bool QueryResultsPreviewed()
10681072
{
10691073
var previewed = PreviewSelectedItem == Results.SelectedItem;
@@ -1196,6 +1200,7 @@ private void QueryHistory()
11961200
private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true)
11971201
{
11981202
_updateSource?.Cancel();
1203+
_runningQuery = null;
11991204

12001205
var query = await ConstructQueryAsync(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts);
12011206

@@ -1215,89 +1220,103 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b
12151220
return;
12161221
}
12171222

1218-
_updateSource = new CancellationTokenSource();
1223+
try
1224+
{
1225+
// Check if the query has changed because query can be changed so fast that
1226+
// token of the query between two queries has not been created yet
1227+
if (query.Input != QueryText) return;
12191228

1220-
ProgressBarVisibility = Visibility.Hidden;
1221-
_isQueryRunning = true;
1229+
_updateSource = new CancellationTokenSource();
12221230

1223-
// Switch to ThreadPool thread
1224-
await TaskScheduler.Default;
1231+
ProgressBarVisibility = Visibility.Hidden;
12251232

1226-
if (_updateSource.Token.IsCancellationRequested) return;
1233+
_runningQuery = query;
1234+
_currentQuery = query;
12271235

1228-
// Update the query's IsReQuery property to true if this is a re-query
1229-
query.IsReQuery = isReQuery;
1236+
// Switch to ThreadPool thread
1237+
await TaskScheduler.Default;
12301238

1231-
// handle the exclusiveness of plugin using action keyword
1232-
RemoveOldQueryResults(query);
1239+
if (_updateSource.Token.IsCancellationRequested) return;
12331240

1234-
_lastQuery = query;
1241+
// Update the query's IsReQuery property to true if this is a re-query
1242+
query.IsReQuery = isReQuery;
12351243

1236-
var plugins = PluginManager.ValidPluginsForQuery(query);
1244+
// handle the exclusiveness of plugin using action keyword
1245+
RemoveOldQueryResults(query);
12371246

1238-
if (plugins.Count == 1)
1239-
{
1240-
PluginIconPath = plugins.Single().Metadata.IcoPath;
1241-
PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
1242-
SearchIconVisibility = Visibility.Hidden;
1243-
}
1244-
else
1245-
{
1246-
PluginIconPath = null;
1247-
PluginIconSource = null;
1248-
SearchIconVisibility = Visibility.Visible;
1249-
}
1247+
_lastQuery = query;
12501248

1251-
// Do not wait for performance improvement
1252-
/*if (string.IsNullOrEmpty(query.ActionKeyword))
1253-
{
1254-
// Wait 15 millisecond for query change in global query
1255-
// if query changes, return so that it won't be calculated
1256-
await Task.Delay(15, _updateSource.Token);
1257-
if (_updateSource.Token.IsCancellationRequested)
1258-
return;
1259-
}*/
1249+
var plugins = PluginManager.ValidPluginsForQuery(query);
12601250

1261-
_ = Task.Delay(200, _updateSource.Token).ContinueWith(_ =>
1251+
if (plugins.Count == 1)
1252+
{
1253+
PluginIconPath = plugins.Single().Metadata.IcoPath;
1254+
PluginIconSource = await App.API.LoadImageAsync(PluginIconPath);
1255+
SearchIconVisibility = Visibility.Hidden;
1256+
}
1257+
else
1258+
{
1259+
PluginIconPath = null;
1260+
PluginIconSource = null;
1261+
SearchIconVisibility = Visibility.Visible;
1262+
}
1263+
1264+
// Do not wait for performance improvement
1265+
/*if (string.IsNullOrEmpty(query.ActionKeyword))
1266+
{
1267+
// Wait 15 millisecond for query change in global query
1268+
// if query changes, return so that it won't be calculated
1269+
await Task.Delay(15, _updateSource.Token);
1270+
if (_updateSource.Token.IsCancellationRequested)
1271+
return;
1272+
}*/
1273+
1274+
_ = Task.Delay(200, _updateSource.Token).ContinueWith(_ =>
12621275
{
12631276
// start the progress bar if query takes more than 200 ms and this is the current running query and it didn't finish yet
1264-
if (_isQueryRunning)
1277+
if (_runningQuery != null && _runningQuery == query)
12651278
{
12661279
ProgressBarVisibility = Visibility.Visible;
12671280
}
12681281
},
1269-
_updateSource.Token,
1270-
TaskContinuationOptions.NotOnCanceled,
1271-
TaskScheduler.Default);
1282+
_updateSource.Token,
1283+
TaskContinuationOptions.NotOnCanceled,
1284+
TaskScheduler.Default);
12721285

1273-
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
1286+
// plugins are ICollection, meaning LINQ will get the Count and preallocate Array
12741287

1275-
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
1276-
{
1277-
false => QueryTaskAsync(plugin, _updateSource.Token),
1278-
true => Task.CompletedTask
1279-
}).ToArray();
1288+
var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch
1289+
{
1290+
false => QueryTaskAsync(plugin, _updateSource.Token),
1291+
true => Task.CompletedTask
1292+
}).ToArray();
12801293

1281-
try
1282-
{
1283-
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
1284-
await Task.WhenAll(tasks);
1285-
}
1286-
catch (OperationCanceledException)
1287-
{
1288-
// nothing to do here
1289-
}
1294+
try
1295+
{
1296+
// Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first
1297+
await Task.WhenAll(tasks);
1298+
}
1299+
catch (OperationCanceledException)
1300+
{
1301+
// nothing to do here
1302+
}
12901303

1291-
if (_updateSource.Token.IsCancellationRequested) return;
1304+
if (_updateSource.Token.IsCancellationRequested) return;
12921305

1293-
// this should happen once after all queries are done so progress bar should continue
1294-
// until the end of all querying
1295-
_isQueryRunning = false;
1306+
// this should happen once after all queries are done so progress bar should continue
1307+
// until the end of all querying
1308+
_runningQuery = null;
12961309

1297-
if (!_updateSource.Token.IsCancellationRequested)
1310+
if (!_updateSource.Token.IsCancellationRequested)
1311+
{
1312+
// update to hidden if this is still the current query
1313+
ProgressBarVisibility = Visibility.Hidden;
1314+
}
1315+
}
1316+
finally
12981317
{
1299-
// update to hidden if this is still the current query
1300-
ProgressBarVisibility = Visibility.Hidden;
1318+
// this make sures running query is null even if the query is canceled
1319+
_runningQuery = null;
13011320
}
13021321

13031322
// Local function
@@ -1374,7 +1393,7 @@ private async Task<Query> ConstructQueryAsync(string queryText, IEnumerable<Cust
13741393
// Applying builtin shortcuts
13751394
await BuildQueryAsync(builtInShortcuts, queryBuilder, queryBuilderTmp);
13761395

1377-
return QueryBuilder.Build(queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
1396+
return QueryBuilder.Build(QueryText, queryBuilder.ToString().Trim(), PluginManager.NonGlobalPlugins);
13781397
}
13791398

13801399
private async Task BuildQueryAsync(IEnumerable<BaseBuiltinShortcutModel> builtInShortcuts,
@@ -1549,9 +1568,6 @@ public bool ShouldIgnoreHotkeys()
15491568

15501569
public void Show()
15511570
{
1552-
// When application is exiting, we should not show the main window
1553-
if (App.Exiting) return;
1554-
15551571
// When application is exiting, the Application.Current will be null
15561572
Application.Current?.Dispatcher.Invoke(() =>
15571573
{

0 commit comments

Comments
 (0)