From 34c3cdabf799734299b835ae42acb71ec5454b21 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 2 May 2025 12:44:17 +0800 Subject: [PATCH 01/15] Improve update logic --- Flow.Launcher.Core/Plugin/QueryBuilder.cs | 12 +- Flow.Launcher.Plugin/Query.cs | 6 + Flow.Launcher.Test/QueryBuilderTest.cs | 6 +- Flow.Launcher/MainWindow.xaml.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 158 ++++++++++++---------- 5 files changed, 105 insertions(+), 79 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index 0ef3f30f5e1..39dea289583 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -6,12 +6,13 @@ namespace Flow.Launcher.Core.Plugin { public static class QueryBuilder { - public static Query Build(string text, Dictionary nonGlobalPlugins) + public static Query Build(string input, string text, Dictionary nonGlobalPlugins) { // 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 +22,15 @@ public static Query Build(string text, Dictionary nonGlobalP 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; @@ -36,6 +39,7 @@ public static Query Build(string text, Dictionary nonGlobalP return new Query() { Search = search, + Input = input, RawQuery = rawQuery, SearchTerms = searchTerms, ActionKeyword = actionKeyword diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index c3eede4c6b6..86a95a6920b 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -7,6 +7,12 @@ namespace Flow.Launcher.Plugin /// public class Query { + /// + /// Input text in query box. + /// We didn't recommend use this property directly. You should always use Search property. + /// + public string Input { get; internal init; } + /// /// 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. diff --git a/Flow.Launcher.Test/QueryBuilderTest.cs b/Flow.Launcher.Test/QueryBuilderTest.cs index c8ac17748da..3912f26a7d3 100644 --- a/Flow.Launcher.Test/QueryBuilderTest.cs +++ b/Flow.Launcher.Test/QueryBuilderTest.cs @@ -16,7 +16,7 @@ public void ExclusivePluginQueryTest() {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List {">"}}}} }; - 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.Search, "Search should not start with the ActionKeyword."); @@ -39,7 +39,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest() {">", new PluginPair {Metadata = new PluginMetadata {ActionKeywords = new List {">"}, 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."); @@ -51,7 +51,7 @@ public void ExclusivePluginQueryIgnoreDisabledTest() [Test] public void GenericPluginQueryTest() { - Query q = QueryBuilder.Build("file.txt file2 file3", new Dictionary()); + Query q = QueryBuilder.Build("file.txt file2 file3", "file.txt file2 file3", new Dictionary()); ClassicAssert.AreEqual("file.txt file2 file3", q.Search); ClassicAssert.AreEqual("", q.ActionKeyword); diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index ae7b098a206..90260df533f 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -408,7 +408,7 @@ private void OnKeyDown(object sender, KeyEventArgs e) && QueryTextBox.CaretIndex == QueryTextBox.Text.Length) { var queryWithoutActionKeyword = - QueryBuilder.Build(QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; + QueryBuilder.Build(QueryTextBox.Text, QueryTextBox.Text.Trim(), PluginManager.NonGlobalPlugins)?.Search; if (FilesFolders.IsLocationPathString(queryWithoutActionKeyword)) { diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2f1ed0f5103..2fe22d36d9e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -31,8 +31,9 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private static readonly string ClassName = nameof(MainViewModel); - private bool _isQueryRunning; private Query _lastQuery; + private Query _runningQuery; // Used for QueryResultAsync + private Query _currentQuery; // Used for ResultsUpdated private string _queryTextBeforeLeaveResults; private readonly FlowLauncherJsonStorage _historyItemsStorage; @@ -235,7 +236,7 @@ public void RegisterResultsUpdatedEvent() var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - if (e.Query.RawQuery != QueryText || e.Token.IsCancellationRequested) + if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || e.Token.IsCancellationRequested) { return; } @@ -255,9 +256,12 @@ public void RegisterResultsUpdatedEvent() PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query); - if (token.IsCancellationRequested) return; + if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || token.IsCancellationRequested) + { + return; + } - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, token))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); @@ -365,7 +369,7 @@ private void LoadContextMenu() [RelayCommand] private void Backspace(object index) { - var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + var query = QueryBuilder.Build(QueryText, QueryText.Trim(), PluginManager.NonGlobalPlugins); // GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\')); @@ -786,7 +790,7 @@ private ResultsViewModel SelectedResults public Visibility ProgressBarVisibility { get; set; } public Visibility MainWindowVisibility { get; set; } - + // This is to be used for determining the visibility status of the main window instead of MainWindowVisibility // because it is more accurate and reliable representation than using Visibility as a condition check public bool MainWindowVisibilityStatus { get; set; } = true; @@ -1063,7 +1067,7 @@ private bool CanExternalPreviewSelectedResult(out string path) path = QueryResultsPreviewed() ? Results.SelectedItem?.Result?.Preview.FilePath : string.Empty; return !string.IsNullOrEmpty(path); } - + private bool QueryResultsPreviewed() { var previewed = PreviewSelectedItem == Results.SelectedItem; @@ -1196,6 +1200,7 @@ private void QueryHistory() private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); + _runningQuery = null; var query = await ConstructQueryAsync(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts); @@ -1215,89 +1220,103 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b return; } - _updateSource = new CancellationTokenSource(); + try + { + // Check if the query has changed because query can be changed so fast that + // token of the query between two queries has not been created yet + if (query.Input != QueryText) return; - ProgressBarVisibility = Visibility.Hidden; - _isQueryRunning = true; + _updateSource = new CancellationTokenSource(); - // Switch to ThreadPool thread - await TaskScheduler.Default; + ProgressBarVisibility = Visibility.Hidden; - if (_updateSource.Token.IsCancellationRequested) return; + _runningQuery = query; + _currentQuery = query; - // Update the query's IsReQuery property to true if this is a re-query - query.IsReQuery = isReQuery; + // Switch to ThreadPool thread + await TaskScheduler.Default; - // handle the exclusiveness of plugin using action keyword - RemoveOldQueryResults(query); + if (_updateSource.Token.IsCancellationRequested) return; - _lastQuery = query; + // Update the query's IsReQuery property to true if this is a re-query + query.IsReQuery = isReQuery; - var plugins = PluginManager.ValidPluginsForQuery(query); + // handle the exclusiveness of plugin using action keyword + RemoveOldQueryResults(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; - } + _lastQuery = query; - // 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, _updateSource.Token); - if (_updateSource.Token.IsCancellationRequested) - return; - }*/ + var plugins = PluginManager.ValidPluginsForQuery(query); - _ = Task.Delay(200, _updateSource.Token).ContinueWith(_ => + 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, _updateSource.Token); + if (_updateSource.Token.IsCancellationRequested) + return; + }*/ + + _ = Task.Delay(200, _updateSource.Token).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 (_runningQuery != null && _runningQuery == query) { ProgressBarVisibility = Visibility.Visible; } }, - _updateSource.Token, - TaskContinuationOptions.NotOnCanceled, - TaskScheduler.Default); + _updateSource.Token, + 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 - var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch - { - false => QueryTaskAsync(plugin, _updateSource.Token), - true => Task.CompletedTask - }).ToArray(); + var tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch + { + false => QueryTaskAsync(plugin, _updateSource.Token), + 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 - } + 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 (_updateSource.Token.IsCancellationRequested) return; + if (_updateSource.Token.IsCancellationRequested) return; - // this should happen once after all queries are done so progress bar should continue - // until the end of all querying - _isQueryRunning = false; + // this should happen once after all queries are done so progress bar should continue + // until the end of all querying + _runningQuery = null; - if (!_updateSource.Token.IsCancellationRequested) + if (!_updateSource.Token.IsCancellationRequested) + { + // update to hidden if this is still the current query + ProgressBarVisibility = Visibility.Hidden; + } + } + finally { - // update to hidden if this is still the current query - ProgressBarVisibility = Visibility.Hidden; + // this make sures running query is null even if the query is canceled + _runningQuery = null; } // Local function @@ -1374,7 +1393,7 @@ private async Task ConstructQueryAsync(string queryText, IEnumerable builtInShortcuts, @@ -1549,9 +1568,6 @@ public bool ShouldIgnoreHotkeys() public void Show() { - // When application is exiting, we should not show the main window - if (App.Exiting) return; - // When application is exiting, the Application.Current will be null Application.Current?.Dispatcher.Invoke(() => { From 381334f5429668da590d12512d3539f7dac158e3 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Fri, 2 May 2025 12:46:25 +0800 Subject: [PATCH 02/15] Improve variable names --- Flow.Launcher/ViewModel/MainViewModel.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 2fe22d36d9e..b6203ec508c 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -32,8 +32,8 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable private static readonly string ClassName = nameof(MainViewModel); private Query _lastQuery; - private Query _runningQuery; // Used for QueryResultAsync - private Query _currentQuery; // Used for ResultsUpdated + private Query _progressQuery; // Used for QueryResultAsync + private Query _updateQuery; // Used for ResultsUpdated private string _queryTextBeforeLeaveResults; private readonly FlowLauncherJsonStorage _historyItemsStorage; @@ -236,7 +236,7 @@ public void RegisterResultsUpdatedEvent() var plugin = (IResultUpdated)pair.Plugin; plugin.ResultsUpdated += (s, e) => { - if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || e.Token.IsCancellationRequested) + if (_updateQuery == null || e.Query.RawQuery != _updateQuery.RawQuery || e.Token.IsCancellationRequested) { return; } @@ -256,7 +256,7 @@ public void RegisterResultsUpdatedEvent() PluginManager.UpdatePluginMetadata(resultsCopy, pair.Metadata, e.Query); - if (_currentQuery == null || e.Query.RawQuery != _currentQuery.RawQuery || token.IsCancellationRequested) + if (_updateQuery == null || e.Query.RawQuery != _updateQuery.RawQuery || token.IsCancellationRequested) { return; } @@ -1200,7 +1200,7 @@ private void QueryHistory() private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { _updateSource?.Cancel(); - _runningQuery = null; + _progressQuery = null; var query = await ConstructQueryAsync(QueryText, Settings.CustomShortcuts, Settings.BuiltinShortcuts); @@ -1230,8 +1230,8 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b ProgressBarVisibility = Visibility.Hidden; - _runningQuery = query; - _currentQuery = query; + _progressQuery = query; + _updateQuery = query; // Switch to ThreadPool thread await TaskScheduler.Default; @@ -1274,7 +1274,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b _ = Task.Delay(200, _updateSource.Token).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 (_runningQuery != null && _runningQuery == query) + if (_progressQuery != null && _progressQuery == query) { ProgressBarVisibility = Visibility.Visible; } @@ -1305,7 +1305,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // this should happen once after all queries are done so progress bar should continue // until the end of all querying - _runningQuery = null; + _progressQuery = null; if (!_updateSource.Token.IsCancellationRequested) { @@ -1316,7 +1316,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b finally { // this make sures running query is null even if the query is canceled - _runningQuery = null; + _progressQuery = null; } // Local function From 77b87498768b6c3b30fca95050cf6135936cda5d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 5 May 2025 08:53:09 +0800 Subject: [PATCH 03/15] Revert error change --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 78ab93fa942..3dcd9d207e3 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1604,6 +1604,9 @@ public bool ShouldIgnoreHotkeys() public void Show() { + // When application is exiting, we should not show the main window + if (App.Exiting) return; + // When application is exiting, the Application.Current will be null Application.Current?.Dispatcher.Invoke(() => { From cf3fc6a562fb32c30a252064ccc10e161d3a3400 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 5 May 2025 21:53:39 +0800 Subject: [PATCH 04/15] Fix build issue & Adjust indent --- Flow.Launcher/ViewModel/MainViewModel.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 1d2023cd996..af5bb7daded 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1339,13 +1339,13 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b }*/ _ = Task.Delay(200, _updateSource.Token).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 (_progressQuery != null && _progressQuery == query) - { - ProgressBarVisibility = Visibility.Visible; - } - }, + { + // 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 (_progressQuery != null && _progressQuery == query) + { + ProgressBarVisibility = Visibility.Visible; + } + }, _updateSource.Token, TaskContinuationOptions.NotOnCanceled, TaskScheduler.Default); @@ -1357,7 +1357,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch { - false => QueryTaskAsync(plugin, _updateSource.Token), + false => QueryTaskAsync(plugin, true, _updateSource.Token), true => Task.CompletedTask }).ToArray(); @@ -1371,7 +1371,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTaskAsync(plugin, _updateSource.Token), + false => QueryTaskAsync(plugin, false, _updateSource.Token), true => Task.CompletedTask }).ToArray(); } @@ -1405,7 +1405,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b } // Local function - async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) + async Task QueryTaskAsync(PluginPair plugin, bool isHomeQuery, CancellationToken token) { App.API.LogDebug(ClassName, $"Wait for querying plugin <{plugin.Metadata.Name}>"); @@ -1482,7 +1482,7 @@ private async Task ConstructQueryAsync(string queryText, IEnumerable Date: Tue, 6 May 2025 11:34:51 +0800 Subject: [PATCH 05/15] Add Input for home query --- Flow.Launcher.Core/Plugin/QueryBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index f2759005471..56136dc423a 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -13,6 +13,7 @@ public static Query Build(string input, string text, Dictionary(), From 8c48cbe0d723430f807e963e46ac8fdd964dd24d Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 7 May 2025 19:38:57 +0800 Subject: [PATCH 06/15] Use currentCancellationToken instead --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f55fea95218..9d77eb8ba0f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1292,7 +1292,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b // Switch to ThreadPool thread await TaskScheduler.Default; - if (_updateSource.Token.IsCancellationRequested) return; + if (currentCancellationToken.IsCancellationRequested) return; // Update the query's IsReQuery property to true if this is a re-query query.IsReQuery = isReQuery; From 4c5b5fb7587f703d853c49c70e80fcbb9ffc7c92 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 7 May 2025 19:39:56 +0800 Subject: [PATCH 07/15] Improve code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9d77eb8ba0f..04f4c9575e2 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1267,6 +1267,8 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b return; } + var isHomeQuery = query.RawQuery == string.Empty; + try { // Check if the query has changed because query can be changed so fast that @@ -1275,8 +1277,6 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>"); - var isHomeQuery = query.RawQuery == string.Empty; - _updateSource?.Dispose(); var currentUpdateSource = new CancellationTokenSource(); @@ -1364,7 +1364,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch { - false => QueryTaskAsync(plugin, true, currentCancellationToken), + false => QueryTaskAsync(plugin, currentCancellationToken), true => Task.CompletedTask }).ToArray(); @@ -1378,7 +1378,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch { - false => QueryTaskAsync(plugin, false, currentCancellationToken), + false => QueryTaskAsync(plugin, currentCancellationToken), true => Task.CompletedTask }).ToArray(); } @@ -1412,7 +1412,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b } // Local function - async Task QueryTaskAsync(PluginPair plugin, bool isHomeQuery, CancellationToken token) + async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) { App.API.LogDebug(ClassName, $"Wait for querying plugin <{plugin.Metadata.Name}>"); From f5993596372dfb810a7d11ec03a93cb08bb5a4d0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Wed, 7 May 2025 19:45:45 +0800 Subject: [PATCH 08/15] Improve code quality --- Flow.Launcher.Core/Plugin/QueryBuilder.cs | 2 +- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index 56136dc423a..9cfba43ea76 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -13,8 +13,8 @@ public static Query Build(string input, string text, Dictionary(), ActionKeyword = string.Empty diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 04f4c9575e2..912d3f57163 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1333,8 +1333,8 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b } } - var validPluginNames = plugins.Select(x => $"<{x.Metadata.Name}>"); - App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", validPluginNames)}"); + App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: " + + $"{string.Join(" ", plugins.Select(x => $"<{x.Metadata.Name}>"))}"); // Do not wait for performance improvement /*if (string.IsNullOrEmpty(query.ActionKeyword)) From 8450e68f992f13e7909c094adea93084a48ff6bf Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 10 May 2025 10:33:23 +0800 Subject: [PATCH 09/15] Remove async --- Flow.Launcher/MainWindow.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index d2a240e5c23..a7af8e1c484 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -105,7 +105,7 @@ private void OnSourceInitialized(object sender, EventArgs e) Win32Helper.DisableControlBox(this); } - private async void OnLoaded(object sender, RoutedEventArgs _) + private void OnLoaded(object sender, RoutedEventArgs _) { // Check first launch if (_settings.FirstLaunch) From bde74631424d7e1fba5a7ee36915f68ab80b05e0 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sat, 10 May 2025 22:02:24 +0800 Subject: [PATCH 10/15] Fix build issue --- Flow.Launcher/ViewModel/MainViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 0586654bc1c..1ab48bf9d5e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1298,7 +1298,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b query.IsReQuery = isReQuery; ICollection plugins = Array.Empty(); - if (isHomeQuery) + if (currentIsHomeQuery) { if (Settings.ShowHomePage) { From 7c12956f31c372603f57cfd20a4691e937857dbc Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 19 May 2025 16:45:56 +0800 Subject: [PATCH 11/15] Clear results when there are no update tasks --- Flow.Launcher/ViewModel/MainViewModel.cs | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index fd01af32e13..060d0fd2c0a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1245,19 +1245,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b if (query == null) // shortcut expanded { - App.API.LogDebug(ClassName, $"Clear query results"); - - // Hide and clear results again because running query may show and add some results - Results.Visibility = Visibility.Collapsed; - Results.Clear(); - - // Reset plugin icon - PluginIconPath = null; - PluginIconSource = null; - SearchIconVisibility = Visibility.Visible; - - // Hide progress bar again because running query may set this to visible - ProgressBarVisibility = Visibility.Hidden; + ClearResults(); return; } @@ -1351,8 +1339,9 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b { if (ShouldClearExistingResultsForNonQuery(plugins)) { - Results.Clear(); - App.API.LogDebug(ClassName, $"Existing results are cleared for non-query"); + // No update tasks and just return + ClearResults(); + return; } tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch @@ -1405,6 +1394,23 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b } // Local function + void ClearResults() + { + App.API.LogDebug(ClassName, $"Clear query results"); + + // Hide and clear results again because running query may show and add some results + Results.Visibility = Visibility.Collapsed; + Results.Clear(); + + // Reset plugin icon + PluginIconPath = null; + PluginIconSource = null; + SearchIconVisibility = Visibility.Visible; + + // Hide progress bar again because running query may set this to visible + ProgressBarVisibility = Visibility.Hidden; + } + async Task QueryTaskAsync(PluginPair plugin, CancellationToken token) { App.API.LogDebug(ClassName, $"Wait for querying plugin <{plugin.Metadata.Name}>"); From 3bf6008d50f3e7384e2fd9c7fa2a9de881d93c19 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Tue, 3 Jun 2025 14:34:24 +0800 Subject: [PATCH 12/15] Update code comments --- Flow.Launcher/ViewModel/MainViewModel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 371a15dc6ad..18607d7266e 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1278,8 +1278,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b try { - // Check if the query has changed because query can be changed so fast that - // token of the query between two queries has not been created yet + // Check if the input text matches the query text if (query.Input != QueryText) return; App.API.LogDebug(ClassName, $"Start query with ActionKeyword <{query.ActionKeyword}> and RawQuery <{query.RawQuery}>"); From 27ea2e4453483f5a6c5ccaa0c0933d633497b302 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Sun, 20 Jul 2025 20:12:17 +0800 Subject: [PATCH 13/15] Improve code quality --- Flow.Launcher/ViewModel/MainViewModel.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index ac5d5c69642..c29a9bd494f 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1333,7 +1333,7 @@ private static List GetHistoryItems(IEnumerable historyItem private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - _updateSource?.Cancel(); + _updateSource?.CancelAsync(); _progressQuery = null; App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1425,16 +1425,16 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b }*/ _ = 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 (_progressQuery != null && _progressQuery == query) { - // 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 (_progressQuery != null && _progressQuery == query) - { - ProgressBarVisibility = Visibility.Visible; - } - }, - currentCancellationToken, - TaskContinuationOptions.NotOnCanceled, - TaskScheduler.Default); + ProgressBarVisibility = Visibility.Visible; + } + }, + currentCancellationToken, + TaskContinuationOptions.NotOnCanceled, + TaskScheduler.Default); // plugins are ICollection, meaning LINQ will get the Count and preallocate Array @@ -1927,7 +1927,7 @@ public async Task SetupDialogJumpAsync(nint handle) if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - _dialogJumpSource?.Cancel(); + _dialogJumpSource?.CancelAsync(); // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource(); From 1c0c9e740f39fab18fcf7d705fd1ea570b458650 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 16:17:54 +0800 Subject: [PATCH 14/15] Await cancel async --- Flow.Launcher/ViewModel/MainViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index c29a9bd494f..5c3560dd987 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1333,7 +1333,7 @@ private static List GetHistoryItems(IEnumerable historyItem private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - _updateSource?.CancelAsync(); + await _updateSource?.CancelAsync(); _progressQuery = null; App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1927,7 +1927,7 @@ public async Task SetupDialogJumpAsync(nint handle) if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - _dialogJumpSource?.CancelAsync(); + await _dialogJumpSource?.CancelAsync(); // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource(); From 5493e5ca07db297ac9e50a4789fab1950e134a01 Mon Sep 17 00:00:00 2001 From: Jack251970 <1160210343@qq.com> Date: Mon, 21 Jul 2025 17:54:10 +0800 Subject: [PATCH 15/15] Fix null exception --- Flow.Launcher/ViewModel/MainViewModel.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 5c3560dd987..2b8c90d7402 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1333,7 +1333,10 @@ private static List GetHistoryItems(IEnumerable historyItem private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true) { - await _updateSource?.CancelAsync(); + if (_updateSource != null) + { + await _updateSource.CancelAsync(); + } _progressQuery = null; App.API.LogDebug(ClassName, $"Start query with text: <{QueryText}>"); @@ -1927,7 +1930,10 @@ public async Task SetupDialogJumpAsync(nint handle) if (DialogJump.DialogJumpWindowPosition == DialogJumpWindowPositions.UnderDialog) { // Cancel the previous Dialog Jump task - await _dialogJumpSource?.CancelAsync(); + if (_dialogJumpSource != null) + { + await _dialogJumpSource.CancelAsync(); + } // Create a new cancellation token source _dialogJumpSource = new CancellationTokenSource();