Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
7883e7a
add result IcoPath & Glyph + handling show badge switching
jjw24 Oct 17, 2025
10d3ba2
update history result icon display logic & remove badge icon logic
jjw24 Oct 19, 2025
26ad473
change getting last history item to get the same result if exists
jjw24 Oct 19, 2025
92551c5
show distinct records when history style is last opened
jjw24 Oct 21, 2025
25aa5bf
show history result sorted descending by execution time
jjw24 Oct 21, 2025
0f26945
remove duplicate history item sort call
jjw24 Oct 21, 2025
7958b17
wip show result icon
jjw24 Nov 20, 2025
d78d313
update to use IcoAbsoluteLocalPath
jjw24 Dec 26, 2025
de0d022
rename last opened class; use inheritance; initialise at startup
jjw24 Dec 31, 2025
6b6a9a9
simplify GetHistoryItems
jjw24 Jan 1, 2026
8d7fa1d
revert QueryHistoryItems property
jjw24 Jan 1, 2026
810725c
add explicit JsonInclude for PluginID internal set
jjw24 Jan 1, 2026
2e8bb35
Merge branch 'dev' into last_history_show_result_icon
jjw24 Jan 1, 2026
20854ba
formatting
jjw24 Jan 1, 2026
7c670de
fix history result highlight and scroll when history mode activated
jjw24 Jan 1, 2026
f9df44a
rename to IcoPathAbsolute
jjw24 Jan 1, 2026
6d6003f
switch to using TrimmedQuery
jjw24 Jan 1, 2026
9c2f239
add method comments for LastOpenedHistoryResult
jjw24 Jan 1, 2026
024eeaf
changed LastOpenHistoryResult's Copy to DeepCopy to clarify intent
jjw24 Jan 2, 2026
101e5e6
fix Glyph optional null when copying
jjw24 Jan 4, 2026
4174b5e
fix title when query history style used
jjw24 Jan 4, 2026
94ebbab
update legacy item to show Query style title
jjw24 Jan 4, 2026
25037e3
update IcoPath if existing is different to the selected
jjw24 Jan 4, 2026
55673cd
add method comments
jjw24 Jan 4, 2026
ea7a2d2
mark PopulateHistoryFromLegacyHistory obsolete
jjw24 Jan 4, 2026
19fa107
minor fixes and adjustments for code quality
jjw24 Jan 4, 2026
216b6f5
Code cleanup
Jack251970 Jan 5, 2026
5f99235
Code cleanup
Jack251970 Jan 5, 2026
177e607
Add code comments
Jack251970 Jan 5, 2026
11645fd
ensure history results ordered by ExecutedDateTime
jjw24 Jan 5, 2026
58d910d
fix deep copy Glyph on LastOpenHistoryResult
jjw24 Jan 5, 2026
f22b644
Fix issue when querying history items in home page
Jack251970 Jan 5, 2026
e6a91a9
Ensure history items have valid icons in QueryHistory
Jack251970 Jan 5, 2026
cf4268d
Fix preview panel blank issue
Jack251970 Jan 5, 2026
5b2fb1f
Revert "Ensure history items have valid icons in QueryHistory"
Jack251970 Jan 5, 2026
e403c41
Change copied icon logic as origin
Jack251970 Jan 5, 2026
c610100
Use QueryManager function
Jack251970 Jan 6, 2026
e404d02
Update the result when executing history item
Jack251970 Jan 6, 2026
26e4529
Update the result when executing history item
Jack251970 Jan 6, 2026
583bf74
Update code comments
Jack251970 Jan 6, 2026
938a87c
Fix preview panel blank issue
Jack251970 Jan 6, 2026
9d9ab1f
Do not allow context menu for history items
Jack251970 Jan 6, 2026
3745c44
Revert `Update the result when executing history item`
Jack251970 Jan 6, 2026
e970bb4
fix icon & glyph display based on selected history style results
jjw24 Jan 13, 2026
9036002
update glyph when history result exists
jjw24 Jan 20, 2026
5c241d7
fix to respect max history result shown setting
jjw24 Jan 20, 2026
4ddb164
adjusted context menu call to exit early when history result selected
jjw24 Jan 20, 2026
657e6ae
add null-safe comparison for glyph
jjw24 Jan 20, 2026
bf44637
revert context menu early exit when history result
jjw24 Jan 20, 2026
8e127d0
Code cleanup
Jack251970 Jan 21, 2026
b703bcd
Try to fix combability issue
Jack251970 Jan 23, 2026
0f0ee9f
Revert "Try to fix combability issue"
Jack251970 Jan 23, 2026
935d41d
Ensure combability of glyph setter
Jack251970 Jan 23, 2026
e9dcd8b
Fix code comments
Jack251970 Jan 23, 2026
248da93
fix preview when toggle history mode via Ctrl + H
jjw24 Jan 26, 2026
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
44 changes: 38 additions & 6 deletions Flow.Launcher.Plugin/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Text.Json.Serialization;

namespace Flow.Launcher.Plugin
{
/// <summary>
/// Describes a result of a <see cref="Query"/> executed by a plugin
/// Describes a result of a <see cref="Query"/> executed by a plugin.
/// This or its child classes is serializable.
/// </summary>
public class Result
{
Expand All @@ -21,6 +23,8 @@

private string _icoPath;

private string _icoPathAbsolute;

private string _copyText = string.Empty;

private string _badgeIcoPath;
Expand Down Expand Up @@ -64,15 +68,27 @@
public string AutoCompleteText { get; set; }

/// <summary>
/// The image to be displayed for the result.
/// Path or URI to the icon image for this result.
/// Updates <IcoPathAbsolute/> appropriately when set.
/// </summary>
/// <value>Can be a local file path or a URL.</value>
/// <remarks>GlyphInfo is prioritized if not null</remarks>
/// <remarks>
/// Preferred usage: provide a path relative to the plugin directory (for example: "Images\icon.png").
/// Because <see cref="IcoPath"/> is serialized, using relative paths keeps the icon reference portable
/// when Flow is moved.
///
/// Accepted formats:
/// - Relative file paths (resolved against <see cref="PluginDirectory"/> into <see cref="IcoPathAbsolute"/>)
/// - Absolute file paths (left as-is)
/// - HTTP/HTTPS URLs (left as-is)
/// - Data URIs (left as-is)
/// </remarks>
public string IcoPath
{
get => _icoPath;
set
{
_icoPath = value;

// As a standard this property will handle prepping and converting to absolute local path for icon image processing
if (!string.IsNullOrEmpty(value)
&& !string.IsNullOrEmpty(PluginDirectory)
Expand All @@ -81,15 +97,23 @@
&& !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
&& !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
{
_icoPath = Path.Combine(PluginDirectory, value);
_icoPathAbsolute = Path.Combine(PluginDirectory, value);
}
else
{
_icoPath = value;
_icoPathAbsolute = value;
}
}
}

/// <summary>
/// Absolute path or URI which is used to load and display the result icon for Flow.
/// This is populated by the <see cref="IcoPath"/> setter.
/// If a relative path was provided to <see cref="IcoPath"/>, this property will contain the resolved
/// absolute local path after combining with <see cref="PluginDirectory"/>.
/// </summary>
public string IcoPathAbsolute => _icoPathAbsolute;

/// <summary>
/// The image to be displayed for the badge of the result.
/// </summary>
Expand Down Expand Up @@ -131,11 +155,13 @@
/// <summary>
/// Delegate to load an icon for this result.
/// </summary>
[JsonIgnore]
public IconDelegate Icon = null;

/// <summary>
/// Delegate to load an icon for the badge of this result.
/// </summary>
[JsonIgnore]
public IconDelegate BadgeIcon = null;

/// <summary>
Expand All @@ -151,6 +177,7 @@
/// Its result determines what happens to Flow Launcher's query form:
/// when true, the form will be hidden; when false, it will stay in focus.
/// </remarks>
[JsonIgnore]
public Func<ActionContext, bool> Action { get; set; }

/// <summary>
Expand All @@ -161,6 +188,7 @@
/// Its result determines what happens to Flow Launcher's query form:
/// when true, the form will be hidden; when false, it will stay in focus.
/// </remarks>
[JsonIgnore]
public Func<ActionContext, ValueTask<bool>> AsyncAction { get; set; }

/// <summary>
Expand Down Expand Up @@ -203,11 +231,13 @@
/// <example>
/// As external information for ContextMenu
/// </example>
[JsonIgnore]
public object ContextData { get; set; }

/// <summary>
/// Plugin ID that generated this result
/// </summary>
[JsonInclude]
public string PluginID { get; internal set; }

/// <summary>
Expand All @@ -223,6 +253,7 @@
/// <summary>
/// Customized Preview Panel
/// </summary>
[JsonIgnore]
public Lazy<UserControl> PreviewPanel { get; set; }

/// <summary>
Expand All @@ -242,7 +273,7 @@
public PreviewInfo Preview { get; set; } = PreviewInfo.Default;

/// <summary>
/// Determines if the user selection count should be added to the score. This can be useful when set to false to allow the result sequence order to be the same everytime instead of changing based on selection.

Check warning on line 276 in Flow.Launcher.Plugin/Result.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`everytime` is not a recognized word. (unrecognized-spelling)
/// </summary>
public bool AddSelectedCount { get; set; } = true;

Expand Down Expand Up @@ -352,6 +383,7 @@
/// <summary>
/// Delegate to get the preview panel's image
/// </summary>
[JsonIgnore]
public IconDelegate PreviewDelegate { get; set; } = null;

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ await API.StopwatchLogInfoAsync(ClassName, "Startup cost", async () =>

await PluginManager.InitializePluginsAsync(_mainVM);

// Refresh the history results after plugins are initialized so that we can parse the absolute icon paths
_mainVM.RefreshLastOpenedHistoryResults();

// Refresh home page after plugins are initialized because users may open main window during plugin initialization
// And home page is created without full plugin list
if (_settings.ShowHomePage && _mainVM.QueryResultsSelected() && string.IsNullOrEmpty(_mainVM.QueryText))
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/Helper/ResultHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Flow.Launcher.Helper;

public static class ResultHelper
{
public static async Task<Result?> PopulateResultsAsync(LastOpenedHistoryItem item)
public static async Task<Result?> PopulateResultsAsync(LastOpenedHistoryResult item)
{
return await PopulateResultsAsync(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
Grid.Column="1"
Margin="5 24 0 0">

<ikw:SimpleStackPanel

Check warning on line 55 in Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml

View workflow job for this annotation

GitHub Actions / Check Spelling

`ikw` is not a recognized word. (unrecognized-spelling)
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Expand Down Expand Up @@ -333,7 +333,7 @@
Margin="18 24 0 0"
HorizontalAlignment="Left"
RenderOptions.BitmapScalingMode="Fant"
Source="{Binding IcoPath, IsAsync=True}" />
Source="{Binding IcoPathAbsolute, IsAsync=True}" />
<Border
x:Name="LabelUpdate"
Height="12"
Expand Down
2 changes: 1 addition & 1 deletion Flow.Launcher/Storage/HistoryItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Flow.Launcher.Storage
{
[Obsolete("Use LastOpenedHistoryItem instead. This class will be removed in future versions.")]
[Obsolete("Use LastOpenedHistoryResult instead. This class will be removed in future versions.")]
public class HistoryItem
{
public string Query { get; set; }
Expand Down
31 changes: 0 additions & 31 deletions Flow.Launcher/Storage/LastOpenedHistoryItem.cs

This file was deleted.

117 changes: 117 additions & 0 deletions Flow.Launcher/Storage/LastOpenedHistoryResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System;
using Flow.Launcher.Plugin;

namespace Flow.Launcher.Storage;

/// <summary>
/// A serializable result used to record the last opened history for reopening results.
/// Inherits common result fields from <see cref="Result"/> and adds the original query and execution time.
/// </summary>
public class LastOpenedHistoryResult : Result
{
/// <summary>
/// The query string from Query.TrimmedQuery property, it is stored as a string instead of the entire Query class <see cref="Result"/>.
/// This is used so results can be reopened or re-run using the serialized query string.
/// </summary>
public string Query { get; set; } = string.Empty;

/// <summary>
/// The local date and time when this result was executed/opened.
/// </summary>
public DateTime ExecutedDateTime { get; set; }

/// <summary>
/// Initializes a new instance of <see cref="LastOpenedHistoryResult"/>.
/// </summary>
public LastOpenedHistoryResult()
{
}

/// <summary>
/// Creates a <see cref="LastOpenedHistoryResult"/> from an existing <see cref="Result"/>.
/// Copies required fields and sets up default reopening actions.
/// </summary>
/// <param name="result">The original result to create history from.</param>
public LastOpenedHistoryResult(Result result)
{
Title = result.Title;
SubTitle = result.SubTitle;
PluginID = result.PluginID;
Query = result.OriginQuery.TrimmedQuery;
OriginQuery = result.OriginQuery;
RecordKey = result.RecordKey;
IcoPath = result.IcoPath;
PluginDirectory = result.PluginDirectory;
Glyph = result.Glyph;
ExecutedDateTime = DateTime.Now;
// Used for Query History style reopening
Action = _ =>
{
App.API.BackToQueryResults();
App.API.ChangeQuery(result.OriginQuery.TrimmedQuery);
return false;
};
// Used for Last Opened History style reopening, currently need to be assigned at MainViewModel.cs
AsyncAction = null;
}

/// <summary>
/// Selectively creates a deep copy of the required properties for <see cref="LastOpenedHistoryResult"/>.
/// This copy should be independent of original and full isolated.
/// </summary>
/// <returns>A new <see cref="LastOpenedHistoryResult"/> containing the same required data.</returns>
public LastOpenedHistoryResult DeepCopy()
{
// queryValue and glyphValue are captured to ensure they are correctly referenced in the Action delegate.
var queryValue = Query;
var glyphValue = Glyph;
return new LastOpenedHistoryResult
{
Title = Title,
SubTitle = SubTitle,
PluginID = PluginID,
Query = Query,
OriginQuery = new Query { TrimmedQuery = Query },
RecordKey = RecordKey,
IcoPath = IcoPath,
PluginDirectory = PluginDirectory,
// Used for Query History style reopening
Action = _ =>
{
App.API.BackToQueryResults();
App.API.ChangeQuery(queryValue);
return false;
},
// Used for Last Opened History style reopening, currently need to be assigned at MainViewModel.cs
AsyncAction = null,
Glyph = glyphValue != null
? new GlyphInfo(glyphValue.FontFamily, glyphValue.Glyph)
: null,
ExecutedDateTime = ExecutedDateTime
// Note: Other properties are left as default — copy if needed.
};
}

/// <summary>
/// Determines whether the specified <see cref="Result"/> is equivalent to this history result.
/// Comparison uses <see cref="Result.RecordKey"/> when available; otherwise falls back to title/subtitle/plugin id and query.
/// </summary>
/// <param name="r">The result to compare to.</param>
/// <returns><c>true</c> if the results are considered equal; otherwise <c>false</c>.</returns>
public bool Equals(Result r)
{
if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey))
{
return Title == r.Title
&& SubTitle == r.SubTitle
&& PluginID == r.PluginID
&& Query == r.OriginQuery.TrimmedQuery;
}
else
{
return RecordKey == r.RecordKey
&& PluginID == r.PluginID
&& Query == r.OriginQuery.TrimmedQuery;
}
}
}
Loading
Loading