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
Show file tree
Hide file tree
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
Expand Up @@ -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
};
}

Expand Down Expand Up @@ -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
};
}
}
Expand Down
5 changes: 5 additions & 0 deletions Flow.Launcher.Plugin/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

View workflow job for this annotation

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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

InitializeComponent();
Expand All @@ -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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

#pragma warning restore VSTHRD100 // Avoid async void methods

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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

#endregion

Expand All @@ -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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

private async void OnContextMenusForSettingsClick(object sender, RoutedEventArgs e)
{
Expand All @@ -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

View workflow job for this annotation

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

View workflow job for this annotation

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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

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

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

View workflow job for this annotation

GitHub Actions / Check Spelling

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

Expand Down Expand Up @@ -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");
}
Expand All @@ -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");
}
Expand Down Expand Up @@ -1865,6 +1876,7 @@ public void UpdateResultView(ICollection<ResultsForUpdate> resultsForUpdates)
{
if (!resultsForUpdates.Any())
return;

CancellationToken token;

try
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/ViewModel/ResultsForUpdate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
43 changes: 27 additions & 16 deletions Flow.Launcher/ViewModel/ResultsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand Down
Loading