mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Improve and fix query result update logic issue & provide access to exact query typed by user (#3502)
This commit is contained in:
parent
38c0fae064
commit
48f67b1886
13 changed files with 201 additions and 166 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue