Skip to content

Fix result clearing for non-query calls and home page + history results page toggling #3553

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 8 commits into from
May 19, 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
39 changes: 26 additions & 13 deletions Flow.Launcher.Infrastructure/UserSettings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,20 @@ public bool ShowHomePage
}
}

public bool ShowHistoryResultsForHomePage { get; set; } = false;
private bool _showHistoryResultsForHomePage = false;
public bool ShowHistoryResultsForHomePage
{
get => _showHistoryResultsForHomePage;
set
{
if (_showHistoryResultsForHomePage != value)
{
_showHistoryResultsForHomePage = value;
OnPropertyChanged();
}
}
}

public int MaxHistoryResultsToShowForHomePage { get; set; } = 5;

public int CustomExplorerIndex { get; set; } = 0;
Expand Down Expand Up @@ -395,29 +408,29 @@ public List<RegisteredHotkeyData> RegisteredHotkeys
var list = FixedHotkeys();

// Customizeable hotkeys
if(!string.IsNullOrEmpty(Hotkey))
if (!string.IsNullOrEmpty(Hotkey))
list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = ""));
if(!string.IsNullOrEmpty(PreviewHotkey))
if (!string.IsNullOrEmpty(PreviewHotkey))
list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = ""));
if(!string.IsNullOrEmpty(AutoCompleteHotkey))
if (!string.IsNullOrEmpty(AutoCompleteHotkey))
list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = ""));
if(!string.IsNullOrEmpty(AutoCompleteHotkey2))
if (!string.IsNullOrEmpty(AutoCompleteHotkey2))
list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = ""));
if(!string.IsNullOrEmpty(SelectNextItemHotkey))
if (!string.IsNullOrEmpty(SelectNextItemHotkey))
list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = ""));
if(!string.IsNullOrEmpty(SelectNextItemHotkey2))
if (!string.IsNullOrEmpty(SelectNextItemHotkey2))
list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = ""));
if(!string.IsNullOrEmpty(SelectPrevItemHotkey))
if (!string.IsNullOrEmpty(SelectPrevItemHotkey))
list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevItemHotkey2))
if (!string.IsNullOrEmpty(SelectPrevItemHotkey2))
list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = ""));
if(!string.IsNullOrEmpty(SettingWindowHotkey))
if (!string.IsNullOrEmpty(SettingWindowHotkey))
list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = ""));
if(!string.IsNullOrEmpty(OpenContextMenuHotkey))
if (!string.IsNullOrEmpty(OpenContextMenuHotkey))
list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = ""));
if(!string.IsNullOrEmpty(SelectNextPageHotkey))
if (!string.IsNullOrEmpty(SelectNextPageHotkey))
list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = ""));
if(!string.IsNullOrEmpty(SelectPrevPageHotkey))
if (!string.IsNullOrEmpty(SelectPrevPageHotkey))
list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = ""));
if (!string.IsNullOrEmpty(CycleHistoryUpHotkey))
list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = ""));
Expand Down
3 changes: 2 additions & 1 deletion Flow.Launcher/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
private bool _isArrowKeyPressed = false;

// Window Sound Effects
private MediaPlayer animationSoundWMP;

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

View workflow job for this annotation

GitHub Actions / Check Spelling

`WMP` is not a recognized word. (unrecognized-spelling)
private SoundPlayer animationSoundWPF;

// Window WndProc
Expand All @@ -79,9 +79,9 @@

public MainWindow()
{
_settings = Ioc.Default.GetRequiredService<Settings>();

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

View workflow job for this annotation

GitHub Actions / Check Spelling

`Ioc` is not a recognized word. (unrecognized-spelling)
_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 @@ -108,12 +108,12 @@
{
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);
}

private async void OnLoaded(object sender, RoutedEventArgs _)
private void OnLoaded(object sender, RoutedEventArgs _)
{
// Check first launch
if (_settings.FirstLaunch)
Expand Down Expand Up @@ -283,6 +283,7 @@
InitializeContextMenu();
break;
case nameof(Settings.ShowHomePage):
case nameof(Settings.ShowHistoryResultsForHomePage):
if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText))
{
_viewModel.QueryResults();
Expand Down Expand Up @@ -332,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 @@ -456,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 @@ -471,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 @@ -483,11 +484,11 @@
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)
{
Expand Down
56 changes: 43 additions & 13 deletions Flow.Launcher/ViewModel/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ public void RegisterResultsUpdatedEvent()

if (token.IsCancellationRequested) return;

if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
if (!_resultsUpdateChannelWriter.TryWrite(new ResultsForUpdate(resultsCopy, pair.Metadata, e.Query,
token)))
{
App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
Expand Down Expand Up @@ -791,7 +791,7 @@ private ResultsViewModel SelectedResults

public Visibility ProgressBarVisibility { get; set; }
public Visibility MainWindowVisibility { get; set; }

// This is to be used for determining the visibility status of the main window instead of MainWindowVisibility
// because it is more accurate and reliable representation than using Visibility as a condition check
public bool MainWindowVisibilityStatus { get; set; } = true;
Expand Down Expand Up @@ -1068,7 +1068,7 @@ private bool CanExternalPreviewSelectedResult(out string path)
path = QueryResultsPreviewed() ? Results.SelectedItem?.Result?.Preview.FilePath : string.Empty;
return !string.IsNullOrEmpty(path);
}

private bool QueryResultsPreviewed()
{
var previewed = PreviewSelectedItem == Results.SelectedItem;
Expand Down Expand Up @@ -1278,8 +1278,6 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b
// 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)
{
Expand Down Expand Up @@ -1310,8 +1308,7 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b
}
}

var validPluginNames = plugins.Select(x => $"<{x.Metadata.Name}>");
App.API.LogDebug(ClassName, $"Valid <{plugins.Count}> plugins: {string.Join(" ", validPluginNames)}");
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))
Expand Down Expand Up @@ -1339,6 +1336,12 @@ private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, b
Task[] tasks;
if (currentIsHomeQuery)
{
if (ShouldClearExistingResultsForNonQuery(plugins))
{
Results.Clear();
App.API.LogDebug(ClassName, $"Existing results are cleared for non-query");
}

tasks = plugins.Select(plugin => plugin.Metadata.HomeDisabled switch
{
false => QueryTaskAsync(plugin, currentCancellationToken),
Expand Down Expand Up @@ -1430,7 +1433,7 @@ 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 = ShouldClearExistingResults(query, currentIsHomeQuery);
var shouldClearExistingResults = ShouldClearExistingResultsForQuery(query, currentIsHomeQuery);
_lastQuery = query;
_previousIsHomeQuery = currentIsHomeQuery;

Expand All @@ -1452,8 +1455,13 @@ 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)))
token, reSelect, shouldClearExistingResults)))
{
App.API.LogError(ClassName, "Unable to add item to Result Update Queue");
}
Expand Down Expand Up @@ -1539,7 +1547,9 @@ private async Task BuildQueryAsync(IEnumerable<BaseBuiltinShortcutModel> builtIn

/// <summary>
/// Determines whether the existing search results should be cleared based on the current query and the previous query type.
/// This is needed because of the design that treats plugins with action keywords and global action keywords separately. Results are gathered
/// This is used to indicate to QueryTaskAsync or QueryHistoryTask whether to clear results. If both QueryTaskAsync and QueryHistoryTask
/// are not called then use ShouldClearExistingResultsForNonQuery instead.
/// This method needed because of the design that treats plugins with action keywords and global action keywords separately. Results are gathered
/// either from plugins with matching action keywords or global action keyword, but not both. So when the current results are from plugins
/// with a matching action keyword and a new result set comes from a new query with the global action keyword, the existing results need to be cleared,
/// and vice versa. The same applies to home page query results.
Expand All @@ -1550,19 +1560,39 @@ private async Task BuildQueryAsync(IEnumerable<BaseBuiltinShortcutModel> builtIn
/// <param name="query">The current query.</param>
/// <param name="currentIsHomeQuery">A flag indicating if the current query is a home query.</param>
/// <returns>True if the existing results should be cleared, false otherwise.</returns>
private bool ShouldClearExistingResults(Query query, bool currentIsHomeQuery)
private bool ShouldClearExistingResultsForQuery(Query query, bool currentIsHomeQuery)
{
// If previous or current results are from home query, we need to clear them
if (_previousIsHomeQuery || currentIsHomeQuery)
{
App.API.LogDebug(ClassName, $"Cleared old results");
App.API.LogDebug(ClassName, $"Existing results should be cleared for query");
return true;
}

// If the last and current query are not home query type, we need to check the action keyword
if (_lastQuery?.ActionKeyword != query?.ActionKeyword)
{
App.API.LogDebug(ClassName, $"Cleared old results");
App.API.LogDebug(ClassName, $"Existing results should be cleared for query");
return true;
}

return false;
}

/// <summary>
/// Determines whether existing results should be cleared for non-query calls.
/// A non-query call is where QueryTaskAsync and QueryHistoryTask methods are both not called.
/// QueryTaskAsync and QueryHistoryTask both handle result updating (clearing if required) so directly calling
/// Results.Clear() is not required. However when both are not called, we need to directly clear results and this
/// method determines on the condition when clear results should happen.
/// </summary>
/// <param name="plugins">The collection of plugins to check.</param>
/// <returns>True if existing results should be cleared, false otherwise.</returns>
private bool ShouldClearExistingResultsForNonQuery(ICollection<PluginPair> plugins)
{
if (!Settings.ShowHistoryResultsForHomePage && (plugins.Count == 0 || plugins.All(x => x.Metadata.HomeDisabled == true)))
{
App.API.LogDebug(ClassName, $"Existing results should be cleared for non-query");
return true;
}

Expand Down
3 changes: 3 additions & 0 deletions Flow.Launcher/ViewModel/ResultsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,10 @@ private List<ResultViewModel> NewResults(ICollection<ResultsForUpdate> resultsFo
var newResults = resultsForUpdates.SelectMany(u => u.Results, (u, r) => new ResultViewModel(r, _settings));

if (resultsForUpdates.Any(x => x.shouldClearExistingResults))
{
App.API.LogDebug("NewResults", $"Existing results are cleared for query");
return newResults.OrderByDescending(rv => rv.Result.Score).ToList();
}

return Results.Where(r => r?.Result != null && resultsForUpdates.All(u => u.ID != r.Result.PluginID))
.Concat(newResults)
Expand Down
Loading