diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index fae821736fb..25a32a728d3 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -16,7 +16,8 @@ public static Query Build(string text, Dictionary nonGlobalP Search = string.Empty, RawQuery = string.Empty, SearchTerms = Array.Empty(), - ActionKeyword = string.Empty + ActionKeyword = string.Empty, + IsHomeQuery = true }; } @@ -53,7 +54,8 @@ public static Query Build(string text, Dictionary nonGlobalP Search = search, RawQuery = rawQuery, SearchTerms = searchTerms, - ActionKeyword = actionKeyword + ActionKeyword = actionKeyword, + IsHomeQuery = false }; } } diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index c3eede4c6b6..f50614699fd 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -21,6 +21,11 @@ public class Query /// public bool IsReQuery { get; internal set; } = false; + /// + /// Determines whether the query is a home query. + /// + public bool IsHomeQuery { get; internal init; } = false; + /// /// Search part of a query. /// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as RawQuery. diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 0048c8aa964..f15091e4a8d 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -101,7 +101,7 @@ public MainWindow() private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args) { - _theme.RefreshFrameAsync(); + _ = _theme.RefreshFrameAsync(); } private void OnSourceInitialized(object sender, EventArgs e) diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 8c2aeacaefd..64a39fa6279 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -216,7 +216,26 @@ async Task UpdateActionAsync() while (channelReader.TryRead(out var item)) { if (!item.Token.IsCancellationRequested) + { + // Indicate if to clear existing results so to show only ones from plugins with action keywords + var query = item.Query; + var currentIsHomeQuery = query.IsHomeQuery; + var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); + _lastQuery = item.Query; + _previousIsHomeQuery = currentIsHomeQuery; + + // If the queue already has the item, we need to pass the shouldClearExistingResults flag + if (queue.TryGetValue(item.ID, out var existingItem)) + { + item.ShouldClearExistingResults = shouldClearExistingResults || existingItem.ShouldClearExistingResults; + } + else + { + item.ShouldClearExistingResults = shouldClearExistingResults; + } + queue[item.ID] = item; + } } UpdateResultView(queue.Values); @@ -268,6 +287,8 @@ public void RegisterResultsUpdatedEvent() if (token.IsCancellationRequested) return; + App.API.LogDebug(ClassName, $"Update results for plugin <{pair.Metadata.Name}>"); + if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query, token))) { @@ -1262,7 +1283,7 @@ 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 currentIsHomeQuery = query.RawQuery == string.Empty; + var currentIsHomeQuery = query.IsHomeQuery; _updateSource?.Dispose(); @@ -1436,13 +1457,8 @@ await PluginManager.QueryHomeForPluginAsync(plugin, query, token) : App.API.LogDebug(ClassName, $"Update results for plugin <{plugin.Metadata.Name}>"); - // Indicate if to clear existing results so to show only ones from plugins with action keywords - var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); - _lastQuery = query; - _previousIsHomeQuery = currentIsHomeQuery; - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, plugin.Metadata, query, - token, reSelect, shouldClearExistingResults))) + token, reSelect))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } @@ -1459,13 +1475,8 @@ void QueryHistoryTask(CancellationToken token) App.API.LogDebug(ClassName, $"Update results for history"); - // Indicate if to clear existing results so to show only ones from plugins with action keywords - var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery); - _lastQuery = query; - _previousIsHomeQuery = currentIsHomeQuery; - if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(results, _historyMetadata, query, - token, reSelect, shouldClearExistingResults))) + token, reSelect))) { App.API.LogError(ClassName, "Unable to add item to Result Update Queue"); } @@ -1865,6 +1876,7 @@ public void UpdateResultView(ICollection resultsForUpdates) { if (!resultsForUpdates.Any()) return; + CancellationToken token; try diff --git a/Flow.Launcher/ViewModel/ResultsForUpdate.cs b/Flow.Launcher/ViewModel/ResultsForUpdate.cs index 1563f85bae2..304dd875716 100644 --- a/Flow.Launcher/ViewModel/ResultsForUpdate.cs +++ b/Flow.Launcher/ViewModel/ResultsForUpdate.cs @@ -10,7 +10,7 @@ public record struct ResultsForUpdate( Query Query, CancellationToken Token, bool ReSelectFirstResult = true, - bool shouldClearExistingResults = false) + bool ShouldClearExistingResults = false) { public string ID { get; } = Metadata.ID; } diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 9cd36e5c480..2f4ca6ccf36 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -17,6 +17,8 @@ public class ResultsViewModel : BaseModel { #region Private Fields + private readonly string ClassName = nameof(ResultsViewModel); + public ResultCollection Results { get; } private readonly object _collectionLock = new(); @@ -187,11 +189,9 @@ public void AddResults(List newRawResults, string resultId) /// public void AddResults(ICollection resultsForUpdates, CancellationToken token, bool reselect = true) { + // Since NewResults may need to clear existing results, do not check token cancellation after this point var newResults = NewResults(resultsForUpdates); - if (token.IsCancellationRequested) - return; - UpdateResults(newResults, reselect, token); } @@ -240,16 +240,20 @@ private List NewResults(List newRawResults, string resu private List NewResults(ICollection resultsForUpdates) { if (!resultsForUpdates.Any()) + { + App.API.LogDebug(ClassName, "No results for updates, returning existing results"); return Results; + } var newResults = resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings)); - if (resultsForUpdates.Any(x => x.shouldClearExistingResults)) + if (resultsForUpdates.Any(x => x.ShouldClearExistingResults)) { - App.API.LogDebug("NewResults", $"Existing results are cleared for query"); + App.API.LogDebug(ClassName, $"Existing results are cleared for query"); return newResults.OrderByDescending(rv => rv.Result.Score).ToList(); } + App.API.LogDebug(ClassName, $"Keeping existing results for {resultsForUpdates.Count} queries"); return Results.Where(r => r?.Result != null && resultsForUpdates.All(u => u.ID != r.Result.PluginID)) .Concat(newResults) .OrderByDescending(rv => rv.Result.Score) @@ -293,8 +297,6 @@ public class ResultCollection : List, INotifyCollectionChanged { private long editTime = 0; - private CancellationToken _token; - public event NotifyCollectionChangedEventHandler CollectionChanged; protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) @@ -302,12 +304,12 @@ protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e) CollectionChanged?.Invoke(this, e); } - public void BulkAddAll(List resultViews) + private void BulkAddAll(List resultViews, CancellationToken token = default) { AddRange(resultViews); // can return because the list will be cleared next time updated, which include a reset event - if (_token.IsCancellationRequested) + if (token.IsCancellationRequested) return; // manually update event @@ -315,12 +317,12 @@ public void BulkAddAll(List resultViews) OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - private void AddAll(List Items) + private void AddAll(List Items, CancellationToken token = default) { for (int i = 0; i < Items.Count; i++) { var item = Items[i]; - if (_token.IsCancellationRequested) + if (token.IsCancellationRequested) return; Add(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, i)); @@ -342,21 +344,30 @@ public void RemoveAll(int Capacity = 512) /// public void Update(List newItems, CancellationToken token = default) { - _token = token; - if (Count == 0 && newItems.Count == 0 || _token.IsCancellationRequested) + // Since NewResults may need to clear existing results, so we cannot check token cancellation here + if (Count == 0 && newItems.Count == 0) return; if (editTime < 10 || newItems.Count < 30) { if (Count != 0) RemoveAll(newItems.Count); - AddAll(newItems); + + // After results are removed, we need to check the token cancellation + // so that we will not add new items from the cancelled queries + if (token.IsCancellationRequested) return; + + AddAll(newItems, token); editTime++; - return; } else { Clear(); - BulkAddAll(newItems); + + // After results are removed, we need to check the token cancellation + // so that we will not add new items from the cancelled queries + if (token.IsCancellationRequested) return; + + BulkAddAll(newItems, token); if (Capacity > 8000 && newItems.Count < 3000) { Capacity = newItems.Count;