diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs index 148fd969e49..47001914343 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs @@ -100,11 +100,11 @@ private void SetupJsonRPC() RPC = new JsonRpc(handler, new JsonRPCPublicAPI(Context.API)); - RPC.AddLocalRpcMethod("UpdateResults", new Action((rawQuery, response) => + RPC.AddLocalRpcMethod("UpdateResults", new Action((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(); diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index d3a8003c1cc..54712942c28 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -401,7 +401,7 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer { 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 @@ public static async Task> QueryForPluginAsync(PluginPair pair, Quer { 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 @@ public static async Task> QueryHomeForPluginAsync(PluginPair pair, { Title = Localize.pluginStillInitializing(metadata.Name), SubTitle = Localize.pluginStillInitializingSubtitle(), - AutoCompleteText = query.RawQuery, + AutoCompleteText = query.TrimmedQuery, IcoPath = metadata.IcoPath, PluginDirectory = metadata.PluginDirectory, ActionKeywordAssigned = query.ActionKeyword, diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index 25a32a728d3..aac620cce64 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -6,15 +6,16 @@ namespace Flow.Launcher.Core.Plugin { public static class QueryBuilder { - public static Query Build(string text, Dictionary nonGlobalPlugins) + public static Query Build(string originalQuery, string trimmedQuery, Dictionary 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(), ActionKeyword = string.Empty, IsHomeQuery = true @@ -22,14 +23,13 @@ public static Query Build(string text, Dictionary nonGlobalP } // 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 @@ public static Query Build(string text, Dictionary nonGlobalP { // 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 diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index f50614699fd..79e6f7d62d6 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System; +using System.Text.Json.Serialization; namespace Flow.Launcher.Plugin { @@ -7,12 +8,30 @@ namespace Flow.Launcher.Plugin /// public class Query { + /// + /// 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. + /// + public string OriginalQuery { 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. - /// We didn't recommend use this property directly. You should always use Search property. + /// 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. + /// + [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; } + } + + /// + /// 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. /// - public string RawQuery { get; internal init; } + public string TrimmedQuery { get; internal init; } /// /// Determines whether the query was forced to execute again. @@ -28,7 +47,7 @@ public class Query /// /// 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 /// @@ -103,6 +122,6 @@ private string SplitSearch(int index) } /// - public override string ToString() => RawQuery; + public override string ToString() => TrimmedQuery; } } diff --git a/Flow.Launcher.Test/QueryBuilderTest.cs b/Flow.Launcher.Test/QueryBuilderTest.cs index c8ac17748da..0ede781f8ab 100644 --- a/Flow.Launcher.Test/QueryBuilderTest.cs +++ b/Flow.Launcher.Test/QueryBuilderTest.cs @@ -16,9 +16,9 @@ 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.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 @@ 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."); + 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 @@ 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/Helper/ResultHelper.cs b/Flow.Launcher/Helper/ResultHelper.cs index 5f9a69f281e..b8b7ff98ec5 100644 --- a/Flow.Launcher/Helper/ResultHelper.cs +++ b/Flow.Launcher/Helper/ResultHelper.cs @@ -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 PopulateResultsAsync(string pluginId, string rawQuery, string title, string subTitle, string recordKey) + public static async Task 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 { diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index 2b65737da73..06b2dda9eed 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -477,7 +477,7 @@ private void OnKeyDown(object sender, KeyEventArgs e) && 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)) { diff --git a/Flow.Launcher/ResultListBox.xaml.cs b/Flow.Launcher/ResultListBox.xaml.cs index ac51b195c7b..fcc73e9ce1b 100644 --- a/Flow.Launcher/ResultListBox.xaml.cs +++ b/Flow.Launcher/ResultListBox.xaml.cs @@ -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 @@ private void ListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e) } } - - 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 @@ private void ResultList_PreviewMouseLeftButtonDown(object sender, MouseButtonEve 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 @@ private void ResultListBox_OnPreviewMouseRightButtonDown(object sender, MouseBut RightClickResultCommand?.Execute(result.Result); } + private void ResultListBox_OnPreviewMouseUp(object sender, MouseButtonEventArgs e) { if (Mouse.DirectlyOver is not FrameworkElement { DataContext: ResultViewModel result }) diff --git a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs index 47647066c91..7f375e34d35 100644 --- a/Flow.Launcher/Storage/LastOpenedHistoryItem.cs +++ b/Flow.Launcher/Storage/LastOpenedHistoryItem.cs @@ -19,13 +19,13 @@ public bool Equals(Result r) 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; } } } diff --git a/Flow.Launcher/Storage/QueryHistory.cs b/Flow.Launcher/Storage/QueryHistory.cs index 8284f7eea01..6998a4ae3f0 100644 --- a/Flow.Launcher/Storage/QueryHistory.cs +++ b/Flow.Launcher/Storage/QueryHistory.cs @@ -35,7 +35,7 @@ public void PopulateHistoryFromLegacyHistory() 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 @@ public void Add(Result result) Title = result.Title, SubTitle = result.SubTitle, PluginID = result.PluginID, - Query = result.OriginQuery.RawQuery, + Query = result.OriginQuery.TrimmedQuery, RecordKey = result.RecordKey, ExecutedDateTime = DateTime.Now }); diff --git a/Flow.Launcher/Storage/TopMostRecord.cs b/Flow.Launcher/Storage/TopMostRecord.cs index 7fc9dcaaa48..9708eab974b 100644 --- a/Flow.Launcher/Storage/TopMostRecord.cs +++ b/Flow.Launcher/Storage/TopMostRecord.cs @@ -113,7 +113,7 @@ internal class TopMostRecord 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 @@ internal bool IsTopMost(Result result) 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 @@ internal void AddOrUpdate(Result result) 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 @@ internal bool IsTopMost(Result result) // 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 @@ internal int GetTopMostIndex(Result result) // 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 @@ internal void Remove(Result result) // 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 @@ internal void Remove(Result result) 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 @@ internal void AddOrUpdate(Result result) 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(); 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(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; } } } diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index f0f4b257ac4..c35f96e6200 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -35,9 +35,10 @@ public partial class MainViewModel : BaseModel, ISavable, IDisposable, IResultUp 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 @@ public void RegisterResultsUpdatedEvent(PluginPair pair) 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 @@ private void LoadContextMenu() [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 @@ private List GetHistoryItems(IEnumerable historyI 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 @@ private List GetHistoryItems(IEnumerable historyI 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 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b 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 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b return; } - _updateSource?.Dispose(); - - var currentUpdateSource = new CancellationTokenSource(); - _updateSource = currentUpdateSource; - var currentCancellationToken = _updateSource.Token; - _updateToken = currentCancellationToken; + try + { + _updateSource?.Dispose(); - ProgressBarVisibility = Visibility.Hidden; - _isQueryRunning = true; + var currentUpdateSource = new CancellationTokenSource(); + _updateSource = currentUpdateSource; + var currentCancellationToken = _updateSource.Token; + _updateToken = currentCancellationToken; - // Switch to ThreadPool thread - await TaskScheduler.Default; + ProgressBarVisibility = Visibility.Hidden; - if (currentCancellationToken.IsCancellationRequested) return; + _progressQuery = query; + _updateQuery = 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; - ICollection plugins = Array.Empty(); - if (currentIsHomeQuery) - { - if (Settings.ShowHomePage) - { - plugins = PluginManager.ValidPluginsForHomeQuery(); - } + if (currentCancellationToken.IsCancellationRequested) return; - PluginIconPath = null; - PluginIconSource = null; - SearchIconVisibility = Visibility.Visible; - } - else - { - plugins = PluginManager.ValidPluginsForQuery(query, currentIsDialogJump); + // Update the query's IsReQuery property to true if this is a re-query + query.IsReQuery = isReQuery; - if (plugins.Count == 1) - { - PluginIconPath = plugins.Single().Metadata.IcoPath; - PluginIconSource = await App.API.LoadImageAsync(PluginIconPath); - SearchIconVisibility = Visibility.Hidden; - } - else + ICollection plugins = Array.Empty(); + 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}>"))}"); + + // 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(_ => + _ = 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 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b 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(); - tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch + // Query history results for home page firstly so it will be put on top of the results + if (Settings.ShowHistoryResultsForHomePage) + { + QueryHistoryTask(currentCancellationToken); + } + } + else { - false => QueryTaskAsync(plugin, currentCancellationToken), - true => Task.CompletedTask - }).ToArray(); + tasks = plugins.Select(plugin => plugin.Metadata.Disabled 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) + try { - QueryHistoryTask(currentCancellationToken); + // Check the code, WhenAll will translate all type of IEnumerable or Collection to Array, so make an array at first + await Task.WhenAll(tasks); } - } - else - { - tasks = plugins.Select(plugin => plugin.Metadata.Disabled switch + catch (OperationCanceledException) { - 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 - } + // nothing to do here + } - if (currentCancellationToken.IsCancellationRequested) return; + 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; + // 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) + if (!currentCancellationToken.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 progress query is null when this query is canceled + _progressQuery = null; } // Local function @@ -1625,7 +1637,7 @@ private async Task ConstructQueryAsync(string queryText, IEnumerable ConstructQueryAsync(string queryText, IEnumerable builtInShortcuts, diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs index d7b0690828a..21a6945a8d9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/SearchManager.cs @@ -172,7 +172,7 @@ private List EverythingContentSearchResult(Query query) Action = c => { Settings.EnableEverythingContentSearch = true; - Context.API.ChangeQuery(query.RawQuery, true); + Context.API.ChangeQuery(query.TrimmedQuery, true); return false; } }