Skip to content

Use new model to clear results & Fix clear existing results when using IResultUpdate #3588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 2, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Flow.Launcher.Core/Plugin/QueryBuilder.cs
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
Search = string.Empty,
RawQuery = string.Empty,
SearchTerms = Array.Empty<string>(),
ActionKeyword = string.Empty
ActionKeyword = string.Empty,
IsHomeQuery = true
};
}

@@ -53,7 +54,8 @@ public static Query Build(string text, Dictionary<string, PluginPair> nonGlobalP
Search = search,
RawQuery = rawQuery,
SearchTerms = searchTerms,
ActionKeyword = actionKeyword
ActionKeyword = actionKeyword,
IsHomeQuery = false
};
}
}
5 changes: 5 additions & 0 deletions Flow.Launcher.Plugin/Query.cs
Original file line number Diff line number Diff line change
@@ -21,6 +21,11 @@ public class Query
/// </summary>
public bool IsReQuery { get; internal set; } = false;

/// <summary>
/// Determines whether the query is a home query.
/// </summary>
public bool IsHomeQuery { get; internal init; } = false;

/// <summary>
/// Search part of a query.
/// This will not include action keyword if exclusive plugin gets it, otherwise it should be same as RawQuery.
2 changes: 1 addition & 1 deletion Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -80,8 +80,8 @@
public MainWindow()
{
_settings = Ioc.Default.GetRequiredService<Settings>();
_theme = Ioc.Default.GetRequiredService<Theme>();

Check warning on line 83 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Ioc` is not a recognized word. (unrecognized-spelling)
_viewModel = Ioc.Default.GetRequiredService<MainViewModel>();

Check warning on line 84 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Ioc` is not a recognized word. (unrecognized-spelling)
DataContext = _viewModel;

InitializeComponent();
@@ -101,14 +101,14 @@

private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args)
{
_theme.RefreshFrameAsync();
_ = _theme.RefreshFrameAsync();
}

private void OnSourceInitialized(object sender, EventArgs e)
{
var handle = Win32Helper.GetWindowHandle(this, true);
_hwndSource = HwndSource.FromHwnd(handle);
_hwndSource.AddHook(WndProc);

Check warning on line 111 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Wnd` is not a recognized word. (unrecognized-spelling)
Win32Helper.HideFromAltTab(this);
Win32Helper.DisableControlBox(this);
}
@@ -333,7 +333,7 @@
{
try
{
_hwndSource.RemoveHook(WndProc);

Check warning on line 336 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Wnd` is not a recognized word. (unrecognized-spelling)
}
catch (Exception)
{
@@ -457,7 +457,7 @@
}
}

#pragma warning restore VSTHRD100 // Avoid async void methods

Check warning on line 460 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`VSTHRD` is not a recognized word. (unrecognized-spelling)

#endregion

@@ -472,7 +472,7 @@

#region Window Context Menu Event

#pragma warning disable VSTHRD100 // Avoid async void methods

Check warning on line 475 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`VSTHRD` is not a recognized word. (unrecognized-spelling)

private async void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e)
{
@@ -484,13 +484,13 @@
App.API.OpenSettingDialog();
}

#pragma warning restore VSTHRD100 // Avoid async void methods

Check warning on line 487 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`VSTHRD` is not a recognized word. (unrecognized-spelling)

#endregion

#region Window WndProc

Check warning on line 491 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Wnd` is not a recognized word. (unrecognized-spelling)

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

Check warning on line 493 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`Wnd` is not a recognized word. (unrecognized-spelling)
{
if (msg == Win32Helper.WM_ENTERSIZEMOVE)
{
@@ -561,7 +561,7 @@

private void InitSoundEffects()
{
if (_settings.WMPInstalled)

Check warning on line 564 in Flow.Launcher/MainWindow.xaml.cs

GitHub Actions / Check Spelling

`WMP` is not a recognized word. (unrecognized-spelling)
{
animationSoundWMP?.Close();
animationSoundWMP = new MediaPlayer();
38 changes: 25 additions & 13 deletions Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Preview

Copilot AI Jun 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResultsForUpdate is defined as a record struct with an init-only property for ShouldClearExistingResults. Reassigning this property later in MainViewModel may cause compilation issues. Consider changing the property to allow mutation (e.g., using a mutable property with get; set;) if it needs to be updated after initialization.

Copilot uses AI. Check for mistakes.

}
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<ResultsForUpdate> resultsForUpdates)
{
if (!resultsForUpdates.Any())
return;

CancellationToken token;

try
2 changes: 1 addition & 1 deletion Flow.Launcher/ViewModel/ResultsForUpdate.cs
Original file line number Diff line number Diff line change
@@ -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;
}
43 changes: 27 additions & 16 deletions Flow.Launcher/ViewModel/ResultsViewModel.cs
Original file line number Diff line number Diff line change
@@ -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<Result> newRawResults, string resultId)
/// </summary>
public void AddResults(ICollection<ResultsForUpdate> 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<ResultViewModel> NewResults(List<Result> newRawResults, string resu
private List<ResultViewModel> NewResults(ICollection<ResultsForUpdate> 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,34 +297,32 @@ public class ResultCollection : List<ResultViewModel>, INotifyCollectionChanged
{
private long editTime = 0;

private CancellationToken _token;

public event NotifyCollectionChangedEventHandler CollectionChanged;

protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
CollectionChanged?.Invoke(this, e);
}

public void BulkAddAll(List<ResultViewModel> resultViews)
private void BulkAddAll(List<ResultViewModel> 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
// wpf use DirectX / double buffered already, so just reset all won't cause ui flickering
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

private void AddAll(List<ResultViewModel> Items)
private void AddAll(List<ResultViewModel> 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)
/// <param name="newItems"></param>
public void Update(List<ResultViewModel> 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;
Loading
Oops, something went wrong.